Drop delbin, old google format. an1 test used to use google format, but
authorRobert Lipe <robertlipe@gpsbabel.org>
Mon, 29 Feb 2016 00:48:33 +0000 (18:48 -0600)
committerRobert Lipe <robertlipe@gpsbabel.org>
Mon, 29 Feb 2016 00:48:33 +0000 (18:48 -0600)
an1 is on the list to be chopped in the next round.

delbin.cc [deleted file]
deprecated/delbin.cc [new file with mode: 0644]
deprecated/google.cc [new file with mode: 0644]
google.cc [deleted file]
testo.d/deprecated/google.test [new file with mode: 0644]
testo.d/google.test [deleted file]

diff --git a/delbin.cc b/delbin.cc
deleted file mode 100644 (file)
index 56735fc..0000000
--- a/delbin.cc
+++ /dev/null
@@ -1,3373 +0,0 @@
-/*
-       DeLorme PN-20/40 USB "DeLBin" protocol
-
-    Copyright (C) 2009 Paul Cornett, pc-gpsb at bullseye.com
-    Copyright (C) 2005-2014 Robert Lipe, robertlipe+source@gpsbabel.orgg
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
-
- */
-
-#include "defs.h"
-#include "src/core/xmltag.h"
-#include <assert.h>
-#include <stdio.h> /* for atoi, sprintf */
-#include <stdlib.h> // atoi
-
-#define MYNAME "delbin"
-static short_handle mkshort_handle;
-
-/*
-Device documentation:
-"DeLorme Binary GPS Format", delbin_user_interface_format_176.pdf
-obtained here: http://forum.delorme.com/viewtopic.php?t=13846
-
-Notes:
-Initial development was done with a PN-40, firmware 2.4.123299. The test
-device was upgraded to firmware 2.5.165506 during development.
-
-The "data size" in the message header includes the 4 trailer bytes, so it
-is really the size of the whole message minus the header.
-
-Messages do not always start at the beginning of a packet. Every once in a
-while, the start of the next message directly follows the end of the previous
-one, in the same packet.
-
-The time before an unacknowledged message will be retransmitted by the
-device is on the order of 2 to 4 seconds.
-
-Retrieving all tracks at once (using code 0 in message 0xb031) does not
-seem to work, it hangs after the first track, maybe waiting for some
-undocumented response message.
-
-Character encoding is not documented, appears to be 8859-1.
-
-The undocumented messages 0xaa01, 0xb015, 0xb016 and the use of the
-"reserved" byte in message 0xb012 were discovered by examining the data
-transferred between the device and DeLorme Topo 8.0. They may have been
-added in the PN-40 2.5 firmware.
-*/
-
-//-----------------------------------------------------------------------------
-// interface to platform-specific device I/O
-typedef struct {
-  void (*init)(const QString& name);
-  void (*deinit)(void);
-  unsigned(*packet_read)(void*);
-  unsigned(*packet_write)(const void*, unsigned);
-} delbin_os_ops_t;
-
-// really static, only extern so it can be forward declared
-extern delbin_os_ops_t delbin_os_ops;
-
-static unsigned delbin_os_packet_size;
-//-----------------------------------------------------------------------------
-
-// number of times to attempt a transfer before giving up
-#define ATTEMPT_MAX 2
-// seconds to wait for expected message (actual time will be somewhat
-// indeterminate, but at least READ_TIMEOUT - 1)
-#define READ_TIMEOUT 6
-
-// debug output: low, medium, high, higher
-#define DBGLVL_L 1
-#define DBGLVL_M 2
-#define DBGLVL_H 3
-#define DBGLVL_H2 4
-
-// Multiple unit support.
-#define DELBIN_MAX_UNITS 32
-static struct {
-  unsigned int unit_number;
-  const char* unit_serial_number;
-  const char* unit_name;
-} delbin_unit_info[DELBIN_MAX_UNITS];
-static int n_delbin_units;
-
-#define UNKNOWN_ELEV -2000000
-
-#define sizeofarray(x) (sizeof(x) / sizeof(x[0]))
-
-static char* opt_getposn = NULL;
-static char* opt_logs = NULL;
-static char* opt_long_notes = NULL;
-static char* opt_nuke_wpt = NULL;
-static char* opt_nuke_trk = NULL;
-static char* opt_nuke_rte = NULL;
-/* If true, Order hint to match Cache Register and Topo 7 */
-static char* opt_hint_at_end = NULL;
-static char* opt_gcsym = NULL;
-
-
-static arglist_t delbin_args[] = {
-  {
-    "get_posn", &opt_getposn, "Return current position as a waypoint",
-    NULL, ARGTYPE_BOOL, ARG_NOMINMAX
-  },
-  {
-    "logs", &opt_logs, "Include groundspeak logs when writing",
-    NULL, ARGTYPE_BOOL, ARG_NOMINMAX
-  },
-  {
-    "long_notes", &opt_long_notes, "Use long waypoint notes regardless of PN version",
-    NULL, ARGTYPE_BOOL, ARG_NOMINMAX
-  },
-  {
-    "nukewpt", &opt_nuke_wpt, "Delete all waypoints before sending", NULL, ARGTYPE_BOOL,
-    ARG_NOMINMAX
-  },
-  {
-    "nuketrk", &opt_nuke_trk, "Delete all tracks before sending", NULL, ARGTYPE_BOOL,
-    ARG_NOMINMAX
-  },
-  {
-    "nukerte", &opt_nuke_rte, "Delete all routes before sending", NULL, ARGTYPE_BOOL,
-    ARG_NOMINMAX
-  },
-  {"hint_at_end", &opt_hint_at_end, "If true, geocache hint at end of text", NULL, ARGTYPE_BOOL, ARG_NOMINMAX },
-  {"gcsym", &opt_gcsym, "If set to 0, prefer user-provided symbols over Groundspeaks ones for geocaches", "1", ARGTYPE_BOOL, ARG_NOMINMAX },
-  ARG_TERMINATOR
-};
-
-// Whether device understands message 0xb016
-static int use_extended_notes;
-
-// Device capabilities
-static unsigned device_max_waypoint;
-
-static const char* waypoint_symbol(unsigned index);
-static unsigned waypoint_symbol_index(const char* name);
-static int track_color(unsigned index);
-static unsigned track_color_index(int bgr);
-
-static unsigned waypoint_i;
-static unsigned waypoint_n;
-static Waypoint** wp_array;
-
-//-----------------------------------------------------------------------------
-// Message ids and sizes. Only the needed ones are here.
-// Note that "in" and "out" ids are named as in the device documentation,
-// so "in" means to the device, "out" means from.
-#define MSG_ACK 0xaa00
-#define MSG_BREAK 0xaa02
-#define MSG_BREAK_SIZE 33
-#define MSG_CAPABILITIES 0xb001
-#define MSG_DELETE 0xb005
-#define MSG_DELETE_SIZE 67
-#define MSG_ERROR 0xa003
-#define MSG_NAVIGATION 0xa010
-#define MSG_REQUEST_ROUTES 0xb051
-#define MSG_REQUEST_ROUTES_SIZE 65
-#define MSG_REQUEST_TRACKS 0xb031
-#define MSG_REQUEST_TRACKS_SIZE 33
-#define MSG_REQUEST_WAYPOINTS 0xb012
-#define MSG_REQUEST_WAYPOINTS_SIZE 15
-#define MSG_ROUTE_COUNT 0xb050
-#define MSG_ROUTE_HEADER_IN 0xb055
-#define MSG_ROUTE_HEADER_OUT 0xb052
-#define MSG_ROUTE_POINT_IN 0xb056
-#define MSG_ROUTE_POINT_OUT 0xb053
-#define MSG_ROUTE_SHAPE_IN 0xb057
-#define MSG_ROUTE_SHAPE_OUT 0xb054
-#define MSG_SATELLITE_INFO 0xa020
-#define MSG_TRACK_COUNT 0xb030
-#define MSG_TRACK_HEADER_IN 0xb035
-#define MSG_TRACK_HEADER_OUT 0xb032
-#define MSG_TRACK_POINT_IN 0xb036
-#define MSG_TRACK_POINT_OUT 0xb033
-#define MSG_TRANSFER_COMPLETE 0xaa04
-#define MSG_VERSION 0xa001
-#define MSG_WAYPOINT_COUNT 0xb010
-#define MSG_WAYPOINT_IN 0xb014
-#define MSG_WAYPOINT_OUT 0xb013
-// Undocumented:
-// This one looks like MSG_ACK, except it also has a string in it that says
-// something like "device is busy". The expected MSG_ACK usually immediately
-// follows it, so the point of this one is unclear.
-#define MSG_NACK 0xaa01
-// Long waypoint notes
-#define MSG_WAYPOINT_NOTE_IN 0xb016
-#define MSG_WAYPOINT_NOTE_OUT 0xb015
-
-//-----------------------------------------------------------------------------
-// Message structures
-
-// Input Delete Message
-// Message ID: 0xB005
-typedef enum {
-  nuke_type_wpt = 0,
-  nuke_type_trk = 1,
-  nuke_type_rte = 2,
-  // int nuke_map = 3;
-} nuke_type;
-
-typedef enum {
-  nuke_mode_all = 0,
-  nuke_mode_single = 1
-} nuke_mode;
-
-typedef enum {
-  nuke_dest_internal = 0,
-  nuke_dest_sd = 1
-} nuke_dest;
-
-typedef struct {
-  uint8_t type;
-  uint8_t mode;
-  uint8_t location;
-  char object_name[64];
-} msg_delete_t;
-
-// Output Waypoint Message
-// Message ID: 0xB013
-// Input Waypoint Message
-// Message ID: 0xB014
-typedef struct {
-  uint8_t total[4]; // U32
-  uint8_t index[4]; // U32
-  uint8_t year;
-  uint8_t month;
-  uint8_t day;
-  uint8_t hour;
-  uint8_t minute;
-  uint8_t second;
-  uint8_t latitude[4]; // S32 rad * 100000000
-  uint8_t longitude[4]; // S32 rad * 100000000
-  uint8_t elevation[4]; // F32 meters
-  uint8_t color;
-  uint8_t symbol;
-  uint8_t name_size;
-  char name[1];
-  // note_size[2] U16
-  // note[note_size]
-} msg_waypoint_t;
-
-// undocumented, seen with PN-40 2.5 firmware
-// output waypoint note
-// Message ID: 0xB015
-// input waypoint note
-// Message ID: 0xB016
-typedef struct {
-  uint8_t index[2];
-  uint8_t total[2];
-  uint8_t name_size;
-  char name[1];
-  // note_size[2]
-  // note[note_size]
-} msg_waypoint_note_t;
-
-// Output Track Point Message
-// Message ID: 0xB033
-// Input Track Point Message
-// Message ID: 0xB036
-typedef struct {
-  uint8_t total[4]; // U32
-  uint8_t index[4]; // U32
-  uint8_t number;
-  struct {
-    uint8_t year;
-    uint8_t month;
-    uint8_t day;
-    uint8_t hour;
-    uint8_t minute;
-    uint8_t second;
-    uint8_t latitude[4]; // S32 rad * 100000000
-    uint8_t longitude[4]; // S32 rad * 100000000
-    uint8_t elevation[4]; // F32 meters
-    uint8_t speed[2]; // U16 km/h * 10
-    uint8_t heading[2]; // U16 deg * 100
-    uint8_t status;
-  } point[1];
-} msg_track_point_t;
-
-// Output Track Header (Name) Message
-// Message ID: 0xB032
-typedef struct {
-  uint8_t total_tracks[2]; // U16
-  uint8_t number[2]; // U16
-  char name[32];
-  uint8_t total_points[4]; // U32
-  uint8_t year;
-  uint8_t month;
-  uint8_t day;
-  uint8_t hour;
-  uint8_t minute;
-  uint8_t second;
-  uint8_t color[2]; // U16
-  uint8_t distance[4]; // U32 m
-  uint8_t duration[4]; // U32 sec
-  uint8_t comment_size[2]; // U16
-  char comment[1];
-} msg_track_header_t;
-
-// Input Upload Track Header Message
-// Message ID: 0xB035
-typedef struct {
-  char name[32];
-  uint8_t total_points[4]; // U32
-  uint8_t year;
-  uint8_t month;
-  uint8_t day;
-  uint8_t hour;
-  uint8_t minute;
-  uint8_t second;
-  uint8_t color[2]; // U16
-  uint8_t comment_size[2]; // U16
-  char comment[1];
-} msg_track_header_in_t;
-
-// Output Route Shape Message
-// Message ID: 0xB054
-typedef struct {
-  uint8_t total[4]; // U32
-  uint8_t index[4]; // U32
-  uint8_t number;
-  uint8_t reserved;
-  struct {
-    uint8_t latitude[4]; // S32 rad * 100000000
-    uint8_t longitude[4]; // S32 rad * 100000000
-  } point[1];
-} msg_route_shape_t;
-
-// Output Route Point Message
-// Message ID: 0xB053
-// Input Route Itin Point Message
-// Message ID: 0xB056
-typedef struct {
-  uint8_t total[4]; // U32
-  uint8_t index[4]; // U32
-  char name[32];
-  uint8_t latitude[4]; // S32 rad * 100000000
-  uint8_t longitude[4]; // S32 rad * 100000000
-  uint8_t time_from_start[4]; // U32 sec
-  uint8_t distance_from_start[4]; // F32 km
-  uint8_t bearing_in[2]; // U16 deg * 100
-  uint8_t bearing_out[2]; // U16 deg * 100
-  uint8_t bearing_next[2]; // U16 deg * 100
-  uint8_t itinerary_type;
-  uint8_t turn_type;
-  uint8_t road_class[2]; // U16
-  uint8_t feature_code[4]; // U32
-  uint8_t exit_label_size;
-  char exit_label[1];
-  // comment_size U8
-  // comment[comment_size]
-  // shape_pt_count U32
-} msg_route_point_t;
-
-// Output Route Header (Name) Message
-// Message ID: 0xB052
-typedef struct {
-  uint8_t total[2]; // U16
-  uint8_t index[2]; // U16
-  char name[64];
-  uint8_t type;
-  uint8_t total_route_point[4]; // U32
-  uint8_t total_shape_point[4]; // U32
-} msg_route_header_t;
-
-// Input Upload Route Header Message
-// Message ID: 0xB055
-typedef struct {
-  char name[64];
-  uint8_t type;
-  uint8_t total_route_point[4]; // U32
-  uint8_t total_shape_point[4]; // U32
-} msg_route_header_in_t;
-
-// Output Navigation Message
-// Message ID: 0xA010
-typedef struct {
-  uint8_t gps_week[2]; // U16
-  uint8_t time_of_week[8]; // D64 sec
-  uint8_t year[2]; // U16
-  uint8_t month;
-  uint8_t day;
-  uint8_t hour;
-  uint8_t minute;
-  uint8_t second;
-  uint8_t satellites;
-  uint8_t latitude[8]; // D64 deg
-  uint8_t longitude[8]; // D64 deg
-  uint8_t elevation[8]; // D64 meters
-  uint8_t geoid_offset[2]; // S16 meters * 10
-  uint8_t speed[4]; // F32 km/h
-  uint8_t heading[2]; // U16 deg * 100
-  uint8_t magnetic_variation[2]; // S16 deg * 100
-  uint8_t fix_status;
-} msg_navigation_t;
-
-// Output Satellite Info Message
-// Message ID: 0xA020
-typedef struct {
-  uint8_t gps_week[2]; // U16
-  uint8_t time_of_week[8]; // D64 sec
-  uint8_t hdop[2]; // U16
-  uint8_t vdop[2]; // U16
-  uint8_t pdop[2]; // U16
-  uint8_t number;
-  struct {
-    uint8_t prn;
-    uint8_t azimuth[2]; // S16 deg? * 100
-    uint8_t elevation[2]; // S16 deg? * 100
-    uint8_t Cn0[2]; // U16 snr * 100
-    uint8_t status;
-  } sat[1];
-} msg_satellite_t;
-
-// Output Version Message
-// Message ID: 0xA001
-typedef struct {
-  uint8_t firmware_version[4];
-  char company[32];
-  char product[32];
-  char firmware[32];
-  char gps_firmware[48];
-  char serial[16];
-  char extra[16];
-} msg_version_t;
-
-// Output Device Capabilities Message
-// Message ID: 0xB001
-typedef struct {
-  uint8_t max_waypoints[4]; // U32
-  uint8_t max_tracks[2]; // U16
-  uint8_t max_track_points[4]; // U32
-  uint8_t max_routes[2]; // U16
-  uint8_t max_route_points[4]; // U32
-  uint8_t max_route_shape_points[4]; // U32
-  uint8_t max_maps[2]; // U16
-  uint8_t min_map_version[2]; // U16
-  uint8_t max_map_version[2]; // U16
-  uint8_t total_internal_file_memory[4]; // U32
-  uint8_t avail_internal_file_memory[4]; // U32
-  uint8_t total_external_file_memory[4]; // U32
-  uint8_t avail_external_file_memory[4]; // U32
-} msg_capabilities_t;
-
-//-----------------------------------------------------------------------------
-
-#if __APPLE__ || __linux
-#include <sys/time.h>
-#endif
-
-static void
-debug_out(const char* fmt, ...)
-{
-  va_list ap;
-  va_start(ap, fmt);
-  fputs(MYNAME ": ", stderr);
-  vfprintf(stderr, fmt, ap);
-  va_end(ap);
-}
-
-static void
-debug_out_time(const char* s)
-{
-#if __APPLE__ || __linux
-  struct timeval tv;
-  gettimeofday(&tv, NULL);
-  debug_out("%u.%03u %s", (unsigned)tv.tv_sec & 0xf, (unsigned)tv.tv_usec / 1000, s);
-#else
-  debug_out("%u %s", (unsigned)time(NULL) & 0xf, s);
-#endif
-}
-
-//-----------------------------------------------------------------------------
-
-static uint16_t
-checksum(const uint8_t* p, unsigned n)
-{
-  int x = 0;
-  unsigned i;
-  for (i = n / 2; i > 0; i--) {
-    x += *p++;
-    x += *p++ << 8;
-  }
-  if (n & 1) {
-    x += *p;
-  }
-  return (uint16_t)-x;
-}
-
-//-----------------------------------------------------------------------------
-// OS packet read/write wrappers
-
-static unsigned
-packet_read(void* buf)
-{
-  unsigned n = delbin_os_ops.packet_read(buf);
-  if (n == 0) {
-    fatal(MYNAME ": read 0\n");
-  }
-  if (global_opts.debug_level >= DBGLVL_H) {
-    unsigned j;
-    const uint8_t* p = (const uint8_t*) buf;
-
-    debug_out_time("pcktrd");
-    for (j = 0; j < n; j++) {
-      warning(" %02x", p[j]);
-    }
-    if (global_opts.debug_level >= DBGLVL_H2) {
-      warning("  ");
-      for (j = 0; j < n; j++) {
-        int c = p[j];
-        warning("%c", isprint(c) ? c : '.');
-      }
-    }
-    warning("\n");
-  }
-  return n;
-}
-
-static void
-packet_write(const void* buf, unsigned size)
-{
-  unsigned n;
-  if (global_opts.debug_level >= DBGLVL_H) {
-    unsigned j;
-    const uint8_t* p = (const uint8_t*) buf;
-
-    debug_out_time("pcktwr");
-    for (j = 0; j < size; j++) {
-      warning(" %02x", p[j]);
-    }
-    if (global_opts.debug_level >= DBGLVL_H2) {
-      warning("  ");
-      for (j = 0; j < size; j++) {
-        int c = p[j];
-        warning("%c", isprint(c) ? c : '.');
-      }
-    }
-    warning("\n");
-  }
-  n = delbin_os_ops.packet_write(buf, size);
-  if (n != size) {
-    fatal(MYNAME ": short write %u %u\n", size, n);
-  }
-}
-
-//-----------------------------------------------------------------------------
-
-// dynamically sized buffer with space reserved for message header and trailer
-typedef struct {
-  // message data size
-  unsigned size;
-  // buffer size
-  unsigned capacity;
-  uint8_t* buf;
-  // convenience pointer to message data area
-  void* data;
-} message_t;
-
-static void
-message_init(message_t* m)
-{
-  m->capacity = 100;
-  m->buf = (uint8_t*)xmalloc(m->capacity);
-  m->data = m->buf + 2 + 8;
-}
-
-static void
-message_init_size(message_t* m, unsigned size)
-{
-  m->size = size;
-  m->capacity = 2 + 8 + size + 4;
-  m->buf = (uint8_t*)xmalloc(m->capacity);
-  m->data = m->buf + 2 + 8;
-}
-
-static void
-message_free(message_t* m)
-{
-  xfree(m->buf);
-  m->buf = NULL;
-  m->data = NULL;
-}
-
-static void
-message_ensure_size(message_t* m, unsigned size)
-{
-  m->size = size;
-  if (m->capacity < 2 + 8 + size + 4) {
-    m->capacity = 2 + 8 + size + 4;
-    xfree(m->buf);
-    m->buf = (uint8_t*)xmalloc(m->capacity);
-    m->data = m->buf + 2 + 8;
-  }
-}
-
-static unsigned
-message_get_id(const message_t* m)
-{
-  return le_readu16(m->buf + 4);
-}
-
-//-----------------------------------------------------------------------------
-
-static void
-message_write(unsigned msg_id, message_t* m)
-{
-  unsigned chksum;
-  unsigned count;
-  unsigned n;
-  uint8_t* p = m->buf;
-
-  // header (2 start bytes filled in later)
-  p[2] = 0xdb;
-  p[3] = 0xfe;
-  le_write16(p + 4, msg_id);
-  // "data size" includes 4 trailer bytes
-  le_write16(p + 6, m->size + 4);
-  chksum = checksum(p + 2, 6);
-  le_write16(p + 8, chksum);
-  // message data (filled in by caller)
-  chksum = checksum((uint8_t*) m->data, m->size);
-  n = 2 + 8 + m->size;
-  // trailer (checksum and marker bytes)
-  le_write16(p + n, chksum);
-  p[n + 2] = 0xad;
-  p[n + 3] = 0xbc;
-  // size of message not counting packet start bytes
-  count = 8 + m->size + 4;
-  do {
-    const uint8_t save0 = p[0];
-    const uint8_t save1 = p[1];
-    n = delbin_os_packet_size - 2;
-    if (n > count) {
-      n = count;
-    }
-    // doc. says 0x20, device sends 0, probably ignored
-    p[0] = 0x20;
-    // valid bytes in packet after first 2
-    p[1] = n;
-    packet_write(p, 2 + n);
-    p[0] = save0;
-    p[1] = save1;
-    p += n;
-    count -= n;
-  } while (count != 0);
-  if (global_opts.debug_level >= DBGLVL_M) {
-    warning(MYNAME ": sent %x\n", msg_id);
-  }
-}
-
-// read from the payload of a single packet
-static unsigned
-read_depacketize_1(uint8_t** p, unsigned n, int new_packet)
-{
-  static uint8_t buf[256];
-  static unsigned buf_i, buf_n;
-  if (new_packet) {
-    buf_n = 0;
-  }
-  while (buf_n == 0) {
-    packet_read(buf);
-    if (buf[1] <= delbin_os_packet_size - 2) {
-      buf_n = buf[1];
-      buf_i = 2;
-    }
-  }
-  *p = buf + buf_i;
-  if (n > buf_n) {
-    n = buf_n;
-  }
-  buf_n -= n;
-  buf_i += n;
-  return n;
-}
-
-// read from packet payloads until request is fulfilled
-static void
-read_depacketize(uint8_t* buf, unsigned n)
-{
-  while (n) {
-    uint8_t* p;
-    unsigned nn = read_depacketize_1(&p, n, FALSE);
-    memcpy(buf, p, nn);
-    n -= nn;
-    buf += nn;
-  }
-}
-
-// Get one valid message.
-// If a corrupted message with the right id is seen, return failure (0).
-static unsigned
-message_read_1(unsigned msg_id, message_t* m)
-{
-  unsigned id;
-  for (;;) {
-    unsigned total;
-    unsigned n;
-    uint8_t buf[8];
-    uint8_t* p;
-
-    n = read_depacketize_1(&p, 8, FALSE);
-    memset(buf, 0, 8);
-    memcpy(buf, p, n);
-    while (buf[0] != 0xdb || buf[1] != 0xfe || checksum(buf, 6) != le_readu16(buf + 6)) {
-      // try for a message start at the beginning of next packet
-      n = read_depacketize_1(&p, 8, TRUE);
-      memset(buf, 0, 8);
-      memcpy(buf, p, n);
-    }
-    id = le_readu16(buf + 2);
-    total = le_readu16(buf + 4);
-    message_ensure_size(m, total - 4);
-    // copy in message head, really only need id field, do the rest for debugging
-    m->buf[0] = m->buf[1] = 0;
-    memcpy(m->buf + 2, buf, 8);
-    // read message body and trailer
-    read_depacketize((uint8_t*) m->data, total);
-    p = (uint8_t*)m->data + m->size;
-    if (checksum((uint8_t*) m->data, m->size) == le_readu16(p) &&
-        p[2] == 0xad && p[3] == 0xbc) {
-      if (global_opts.debug_level >= DBGLVL_M) {
-        warning(MYNAME ": received %x\n", id);
-      }
-      break;
-    }
-    if (global_opts.debug_level >= DBGLVL_L) {
-      warning(MYNAME ": corrupted message %x\n", id);
-    }
-    if (id == msg_id) {
-      id = 0;
-      break;
-    }
-  }
-  return id;
-}
-
-// Send MSG_ACK for given message
-static void
-message_ack(unsigned id, const message_t* m)
-{
-  message_t ack;
-  char* p1;
-  const char* p2 = (const char*) m->data;
-  switch (id) {
-  case MSG_ACK:
-  case MSG_NACK:
-  case MSG_NAVIGATION:
-  case MSG_SATELLITE_INFO:
-    // don't ack these
-    return;
-  }
-  message_init_size(&ack, 4);
-  p1 = (char*) ack.data;
-  // ack payload is id and body checksum of acked message
-  le_write16(p1, id);
-  p1[2] = p2[m->size];
-  p1[3] = p2[m->size + 1];
-  message_write(MSG_ACK, &ack);
-  message_free(&ack);
-}
-
-// Get specific message, ignoring others. Sends ACK for non-interval messages.
-// Gives up after at least READ_TIMEOUT-1 seconds have passed.
-static int
-message_read(unsigned msg_id, message_t* m)
-{
-  unsigned id;
-  time_t time_start = time(NULL);
-
-  if (global_opts.debug_level >= DBGLVL_M) {
-    warning(MYNAME ": looking for %x\n", msg_id);
-  }
-  for (;;) {
-    id = message_read_1(msg_id, m);
-    if (id == 0) {
-      break;
-    }
-    if (id == MSG_ERROR) {
-      const uint8_t* p = (const uint8_t*) m->data;
-      fatal(MYNAME ": device error %u: \"%s\"\n", *p, p + 1);
-    }
-    message_ack(id, m);
-    if (id == msg_id || time(NULL) - time_start >= READ_TIMEOUT) {
-      break;
-    }
-  }
-  return id == msg_id;
-}
-
-// Read a sequence of messages, up to a MSG_TRANSFER_COMPLETE
-static int
-get_batch(message_t** array, unsigned* n)
-{
-  int success = 1;
-  unsigned array_max = 100;
-  message_t* a = (message_t*) xmalloc(array_max * sizeof(message_t));
-  unsigned i = 0;
-  unsigned id;
-  if (global_opts.debug_level >= DBGLVL_M) {
-    warning(MYNAME ": begin get_batch\n");
-  }
-  do {
-    time_t time_start = time(NULL);
-    if (i == array_max) {
-      message_t* old_a = a;
-      array_max += array_max;
-      a = (message_t*) xmalloc(array_max * sizeof(message_t));
-      memcpy(a, old_a, i * sizeof(message_t));
-      xfree(old_a);
-    }
-    message_init(&a[i]);
-    for (;;) {
-      id = message_read_1(0, &a[i]);
-      switch (id) {
-      case MSG_NAVIGATION:
-        if (time(NULL) - time_start >= READ_TIMEOUT) {
-          success = 0;
-          break;
-        }
-        // fall through
-      case MSG_ACK:
-      case MSG_NACK:
-      case MSG_SATELLITE_INFO:
-        continue;
-      }
-      break;
-    }
-    message_ack(id, &a[i]);
-    i++;
-  } while (success && id != MSG_TRANSFER_COMPLETE);
-  if (success) {
-    *array = a;
-    *n = i - 1;
-    message_free(&a[*n]);
-    if (global_opts.debug_level >= DBGLVL_M) {
-      warning(MYNAME ": end get_batch, %u messages\n", *n);
-    }
-  } else {
-    while (i--) {
-      message_free(&a[i]);
-    }
-    xfree(a);
-    *array = NULL;
-    *n = 0;
-    if (global_opts.debug_level >= DBGLVL_M) {
-      warning(MYNAME ": end get_batch, failed\n");
-    }
-  }
-  return success;
-}
-
-typedef struct {
-  unsigned msg_id;
-  message_t msg;
-} batch_array_t;
-
-static batch_array_t* batch_array;
-
-static unsigned batch_array_max;
-static unsigned batch_array_i;
-
-// add a message to sequence that will later be sent all at once
-static void
-add_to_batch(unsigned id, message_t* m)
-{
-  if (batch_array_i == batch_array_max) {
-    char* old = (char*) batch_array;
-    if (batch_array_max == 0) {
-      batch_array_max = 50;
-    }
-    batch_array_max += batch_array_max;
-    batch_array = (batch_array_t*) xmalloc(batch_array_max * sizeof(*batch_array));
-    if (batch_array_i) {
-      memcpy(batch_array, old, batch_array_i * sizeof(*batch_array));
-      xfree(old);
-    }
-  }
-  batch_array[batch_array_i].msg_id = id;
-  batch_array[batch_array_i].msg = *m;
-  batch_array_i++;
-  memset(m, 0, sizeof(*m));
-}
-
-// send an accumulated sequence of messages
-static void
-send_batch(void)
-{
-  message_t m;
-  const unsigned n = batch_array_i;
-  unsigned i;
-  unsigned progress = 0;
-
-  message_init(&m);
-  if (global_opts.debug_level >= DBGLVL_M) {
-    warning(MYNAME ": begin send_batch, %u messages\n", n);
-  }
-  for (i = 0; i < n; i++) {
-    unsigned timeout_count = 0;
-    time_t time_start = time(NULL);
-
-    // Can't really trigger this off either i or n as we don't
-    // know how the various packets map to actual waypts.
-    if (global_opts.verbose_status &&
-        (batch_array[i].msg_id == MSG_WAYPOINT_IN)) {
-      waypt_status_disp(waypoint_n, ++progress);
-    }
-
-    message_write(batch_array[i].msg_id, &batch_array[i].msg);
-    for (;;) {
-      unsigned id = message_read_1(0, &m);
-      switch (id) {
-      case MSG_ACK:
-        break;
-      case MSG_NAVIGATION:
-        if (time(NULL) - time_start >= 2) {
-          if (timeout_count) {
-            fatal(MYNAME ": send_batch timed out\n");
-          }
-          timeout_count++;
-          if (global_opts.debug_level >= DBGLVL_M) {
-            warning(MYNAME ": re-sending %x\n", batch_array[i].msg_id);
-          }
-          message_write(batch_array[i].msg_id, &batch_array[i].msg);
-          time_start = time(NULL);
-        }
-        // fall through
-      case MSG_NACK:
-      case MSG_SATELLITE_INFO:
-        continue;
-      default:
-        warning(MYNAME ": unexpected response message %x during send_batch\n", id);
-        continue;
-      }
-      break;
-    }
-  }
-  message_read(MSG_TRANSFER_COMPLETE, &m);
-  if (global_opts.debug_level >= DBGLVL_M) {
-    warning(MYNAME ": end send_batch\n");
-  }
-  for (i = n; i--;) {
-    message_free(&batch_array[i].msg);
-  }
-  xfree(batch_array);
-  message_free(&m);
-  batch_array_i = batch_array_max = 0;
-}
-
-//-----------------------------------------------------------------------------
-// Coordinate conversion
-
-static double
-delbin_rad2deg(int32_t x)
-{
-  return x * ((180 / M_PI) / 100000000);
-}
-
-static int32_t
-delbin_deg2rad(double x)
-{
-  return (int32_t)(x * ((M_PI / 180) * 100000000));
-}
-
-//-----------------------------------------------------------------------------
-// Waypoint reading
-
-static time_t
-decode_time(const uint8_t* p)
-{
-  struct tm t;
-  t.tm_year = p[0];
-  t.tm_mon  = p[1] - 1;
-  t.tm_mday = p[2];
-  t.tm_hour = p[3];
-  t.tm_min  = p[4];
-  t.tm_sec  = p[5];
-  return mkgmtime(&t);
-}
-
-static Waypoint*
-decode_waypoint(const void* data)
-{
-  Waypoint* wp = new Waypoint;
-  const msg_waypoint_t* p = (const msg_waypoint_t*)data;
-  const char* s;
-  float f;
-
-  wp->SetCreationTime(decode_time(&p->year));
-  wp->latitude = delbin_rad2deg(le_read32(p->latitude));
-  wp->longitude = delbin_rad2deg(le_read32(p->longitude));
-  f = le_read_float(p->elevation);
-  if (f > UNKNOWN_ELEV) {
-    wp->altitude = f;
-  }
-  wp->icon_descr = waypoint_symbol(p->symbol);
-//  if (!wp->icon_descr.isNull()) {
-//    wp->icon_descr = wp->icon_descr;
-//  }
-  if (p->name_size && p->name[0]) {
-    wp->description = p->name;
-  }
-  s = p->name + p->name_size;
-  if (le_readu16(s) &&  s[2]) {
-    wp->notes = xstrdup(s + 2);
-  }
-  return wp;
-}
-
-static void
-read_waypoints(void)
-{
-  message_t m;
-  message_t* msg_array;
-  unsigned msg_array_n;
-  Waypoint* wp = NULL;
-  unsigned n_point;
-  unsigned notes_i = 0;
-  unsigned notes_max = 0;
-  unsigned i;
-  int attempt = ATTEMPT_MAX;
-
-  message_init(&m);
-  // get number of waypoints
-  for (;;) {
-    m.size = 0;
-    message_write(MSG_WAYPOINT_COUNT, &m);
-    if (message_read(MSG_WAYPOINT_COUNT, &m)) {
-      break;
-    }
-    if (--attempt == 0) {
-      fatal(MYNAME ": reading waypoint count failed\n");
-    }
-  }
-  n_point = le_readu32(m.data);
-  if (global_opts.debug_level >= DBGLVL_L) {
-    warning(MYNAME ": %u waypoints\n", n_point);
-  }
-  if (n_point == 0) {
-    message_free(&m);
-    return;
-  }
-  // get waypoint messages
-  attempt = ATTEMPT_MAX;
-  for (;;) {
-    m.size = MSG_REQUEST_WAYPOINTS_SIZE;
-    memset(m.data, 0, m.size);
-    // This byte is documented as reserved. Setting it to 3 is required to get
-    // extended notes (message 0xb015) with PN-40 firmware 2.5.
-    // Whether it has any effect with earlier firmware or the PN-20 is unknown.
-    ((char*)m.data)[1] = 3;
-    message_write(MSG_REQUEST_WAYPOINTS, &m);
-    if (get_batch(&msg_array, &msg_array_n)) {
-      break;
-    }
-    if (--attempt == 0) {
-      fatal(MYNAME ": reading waypoints failed\n");
-    }
-    if (global_opts.debug_level >= DBGLVL_M) {
-      warning(MYNAME ": timed out reading waypoints, retrying\n");
-    }
-    m.size = MSG_BREAK_SIZE;
-    memset(m.data, 0, m.size);
-    message_write(MSG_BREAK, &m);
-  }
-  message_free(&m);
-  // process waypoint messages
-  for (i = 0; i < msg_array_n; i++) {
-    unsigned id = message_get_id(&msg_array[i]);
-    if (id == MSG_WAYPOINT_OUT) {
-      wp = decode_waypoint(msg_array[i].data);
-      waypt_add(wp);
-      notes_i = 0;
-      notes_max = 0;
-      if (global_opts.debug_level >= DBGLVL_L) {
-        warning(MYNAME ": read waypoint '%s'\n", qPrintable(wp->description));
-      }
-    } else if (wp && id == MSG_WAYPOINT_NOTE_OUT) {
-      const msg_waypoint_note_t* p = (const msg_waypoint_note_t*) msg_array[i].data;
-      const char* s = p->name + p->name_size;
-      unsigned nn = le_readu16(s);
-      if (notes_max < notes_i + nn) {
-#if NEW_STRINGS
-// This section needs a serious rethinking.
-#else
-        char* old = wp->notes;
-#endif
-        if (notes_max == 0) {
-          notes_max = nn;
-        }
-        do {
-          notes_max += notes_max;
-        } while (notes_max < notes_i + nn);
-        wp->notes = (char*) xmalloc(notes_max);
-#if NEW_STRINGS
-#else
-        if (old) {
-          memcpy(wp->notes, old, notes_i);
-          xfree(old);
-        }
-#endif
-      }
-      if (nn) {
-#if NEW_STRINGS
-        // Is this really what this code was trying to do?
-        wp->notes += QString::fromUtf8(s + 2, nn);
-#else
-        memcpy(wp->notes + notes_i, s + 2, nn);
-#endif
-        notes_i += nn;
-        if (wp->notes[notes_i - 1] == 0) {
-          notes_i--;
-        }
-      }
-    } else {
-      fatal(MYNAME ": unexpected message %x while reading waypoints\n", id);
-    }
-    message_free(&msg_array[i]);
-  }
-  xfree(msg_array);
-}
-
-//-----------------------------------------------------------------------------
-// Waypoint writing
-
-static void
-encode_time(time_t time_, uint8_t* p)
-{
-  const struct tm* t = gmtime(&time_);
-  p[0] = t->tm_year;
-  p[1] = t->tm_mon + 1;
-  p[2] = t->tm_mday;
-  p[3] = t->tm_hour;
-  p[4] = t->tm_min;
-  p[5] = t->tm_sec;
-}
-
-static void
-get_gc_notes(const Waypoint* wp, int* symbol, char** notes, unsigned* notes_size)
-{
-  fs_xml* fs_gpx;
-  xml_tag* root = NULL;
-  gbfile* fd = gbfopen(NULL, "w", MYNAME);
-  const char* size = NULL;
-  int gc_sym = 0;
-
-  switch (wp->gc_data->type) {
-  case gt_traditional:
-    gc_sym = 160;
-    break;
-  case gt_multi:
-    gc_sym = 161;
-    break;
-  case gt_virtual:
-    gc_sym = 169;
-    break;
-  case gt_letterbox:
-    gc_sym = 163;
-    break;
-  case gt_event:
-    gc_sym = 165;
-    break;
-  case gt_suprise:
-    gc_sym = 162;
-    break;
-  case gt_webcam:
-    gc_sym = 170;
-    break;
-  case gt_earth:
-    gc_sym = 168;
-    break;
-  case gt_benchmark:
-    gc_sym = 172;
-    break;
-  case gt_cito:
-    gc_sym = 167;
-    break;
-  case gt_mega:
-    gc_sym = 166;
-    break;
-  case gt_wherigo:
-    gc_sym = 164;
-    break;
-  case gt_unknown:
-  case gt_locationless:
-  case gt_ape:
-    break;
-  }
-  if (0 == (wp->icon_descr.compare("Geocache Found"))) {
-    gc_sym = 124;
-  }
-  if (!wp->description.isEmpty()) {
-    gbfputs(wp->description, fd);
-    if (!wp->gc_data->placer.isEmpty()) {
-      gbfprintf(fd, " by %s", CSTR(wp->gc_data->placer));
-    }
-    gbfputc('\n', fd);
-  }
-
-  gbfprintf(fd, "Cache ID: %s\n", CSTRc(wp->shortname));
-  if (gc_sym && opt_gcsym && atoi(opt_gcsym)) {
-    gbfprintf(fd, "%s\n", waypoint_symbol(gc_sym));
-    *symbol = gc_sym;
-  } else if (!wp->icon_descr.isNull()) {
-    gbfprintf(fd, "%s\n", CSTR(wp->icon_descr));
-  }
-  switch (wp->gc_data->container) {
-  case gc_micro:
-    size = "Micro";
-    break;
-  case gc_small:
-    size = "Small";
-    break;
-  case gc_regular:
-    size = "Regular";
-    break;
-  case gc_large:
-    size = "Large";
-    break;
-  case gc_unknown:
-    size = "Not Chosen" ;
-    break;
-  case gc_other:
-    size = "Other";
-    break;
-    // Device has no symbol for this, but this is what Topo sends.
-  case gc_virtual:
-    size = "Virtual";
-    break;
-  default:
-    break;
-  }
-  if (size) {
-    gbfprintf(fd, "SIZE: %s\n", size);
-  }
-  if (wp->gc_data->diff % 10) {
-    gbfprintf(fd, "D%.1f", wp->gc_data->diff / 10.0);
-  } else {
-    gbfprintf(fd, "D%u", wp->gc_data->diff / 10);
-  }
-  if (wp->gc_data->terr % 10) {
-    gbfprintf(fd, "/T%.1f\n", wp->gc_data->terr / 10.0);
-  } else {
-    gbfprintf(fd, "/T%u\n", wp->gc_data->terr / 10);
-  }
-  if (!wp->gc_data->hint.isEmpty() && !opt_hint_at_end) {
-    gbfprintf(fd, "HINT: %s\n", CSTR(wp->gc_data->hint));
-  }
-  if (!wp->gc_data->desc_short.utfstring.isEmpty() || !wp->gc_data->desc_long.utfstring.isEmpty()) {
-    gbfputs("DESC: ", fd);
-    if (!wp->gc_data->desc_short.utfstring.isEmpty()) {
-      char* s1 = strip_html(&wp->gc_data->desc_short);
-      char* s2 = cet_str_utf8_to_any(s1, global_opts.charset);
-      gbfprintf(fd, "%s\n", s2);
-      xfree(s2);
-      xfree(s1);
-    }
-    if (!wp->gc_data->desc_long.utfstring.isEmpty()) {
-      char* s1 = strip_html(&wp->gc_data->desc_long);
-      char* s2 = cet_str_utf8_to_any(s1, global_opts.charset);
-      gbfputs(s2, fd);
-      xfree(s2);
-      xfree(s1);
-    }
-  }
-  fs_gpx = (fs_xml*)fs_chain_find(wp->fs, FS_GPX);
-  if (opt_logs && fs_gpx && fs_gpx->tag) {
-    root = xml_findfirst(fs_gpx->tag, "groundspeak:logs");
-  }
-  if (root) {
-    xml_tag* curlog = xml_findfirst(root, "groundspeak:log");
-    if (curlog) {
-      gbfputs("\nLOG:\n", fd);
-    }
-    for (; curlog; curlog = xml_findnext(root, curlog, "groundspeak:log")) {
-      xml_tag* logpart = xml_findfirst(curlog, "groundspeak:type");
-      if (logpart) {
-        gbfprintf(fd, "%s\n", CSTR(logpart->cdata));
-      }
-      logpart = xml_findfirst(curlog, "groundspeak:date");
-      if (logpart) {
-        time_t logtime = xml_parse_time(logpart->cdata).toTime_t();
-        const struct tm* logtm = gmtime(&logtime);
-        gbfprintf(fd, "%d-%02d-%02d ", logtm->tm_year + 1900, logtm->tm_mon + 1, logtm->tm_mday);
-      }
-      logpart = xml_findfirst(curlog, "groundspeak:finder");
-      if (logpart) {
-        char* s = cet_str_utf8_to_any(CSTR(logpart->cdata), global_opts.charset);
-        gbfputs(s, fd);
-        xfree(s);
-      }
-      logpart = xml_findfirst(curlog, "groundspeak:text");
-      if (logpart) {
-        char* s = cet_str_utf8_to_any(CSTR(logpart->cdata), global_opts.charset);
-        gbfprintf(fd, ", %s", s);
-        xfree(s);
-      }
-      gbfputc('\n', fd);
-    }
-  }
-  if (!wp->gc_data->hint.isEmpty() && opt_hint_at_end) {
-    gbfprintf(fd, "\nHINT: %s\n", CSTR(wp->gc_data->hint));
-  }
-  gbfputc(0, fd);
-  *notes_size = fd->memlen;
-  *notes = (char*) xmalloc(*notes_size);
-  memcpy(*notes, fd->handle.mem, *notes_size);
-  gbfclose(fd);
-}
-
-static void
-write_waypoint_notes(const char* notes, unsigned size, const char* name)
-{
-  message_t m;
-  const unsigned name_size = strlen(name) + 1;
-  const unsigned bytes_per_msg = (10 * (delbin_os_packet_size - 2)) - name_size - 20;
-  const unsigned msg_count = (size + (bytes_per_msg - 1)) / bytes_per_msg;
-  unsigned i = 1;
-
-  do {
-    char* pp;
-    unsigned n = bytes_per_msg;
-    msg_waypoint_note_t* p;
-    message_init_size(&m, 2 + 2 + 1 + name_size + 2 + bytes_per_msg);
-    p = (msg_waypoint_note_t*) m.data;
-    le_write16(p->index, i++);
-    le_write16(p->total, msg_count);
-    p->name_size = name_size;
-    memcpy(p->name, name, p->name_size);
-    pp = p->name + p->name_size;
-    if (n > size) {
-      n = size;
-    }
-    le_write16(pp, n);
-    pp += 2;
-    memcpy(pp, notes, n);
-    pp += n;
-    if (*(pp - 1)) {
-      *pp++ = 0;
-    }
-    notes += n;
-    size -= n;
-    m.size = pp - (char*)p;
-    add_to_batch(MSG_WAYPOINT_NOTE_IN, &m);
-  } while (size != 0);
-}
-
-static void
-add_nuke(nuke_type type)
-{
-  message_t m;
-  msg_delete_t* p;
-
-  message_init_size(&m, MSG_DELETE_SIZE);
-  p = (msg_delete_t*) m.data;
-  p->type = type;
-  p->mode = nuke_mode_all;
-  p->location = nuke_dest_internal;
-  memset(p->object_name, 0, sizeof(p->object_name));
-
-  // MSG_DELETE generates a MSG_TRANSFER_COMPLETE,
-  // so use the batch facility to wait for it
-  add_to_batch(MSG_DELETE, &m);
-  send_batch();
-}
-
-static void
-write_waypoint(const Waypoint* wp)
-{
-  message_t m;
-  msg_waypoint_t* p;
-  QString name = wp->shortname;
-  char* notes;
-  unsigned name_size;
-  unsigned notes_size = 0;
-  unsigned extended_notes_size = 0;
-  const char* notes_freeable = NULL;
-  int symbol = -1;
-  float elev = UNKNOWN_ELEV;
-  char* pp;
-
-  if (wp->EmptyGCData()) {
-    notes = xstrdup(wp->notes);
-    if (notes == NULL && wp->description.isEmpty() && wp->shortname != wp->description) {
-      notes = xstrdup(wp->description);
-    }
-    if (notes) {
-      notes_size = strlen(notes) + 1;
-    }
-  } else {
-    get_gc_notes(wp, &symbol, &notes, &notes_size);
-    notes_freeable = notes;
-    if (!wp->description.isEmpty()) {
-      name = mkshort(mkshort_handle, wp->description);
-    }
-  }
-
-  if (notes_size > 800) {
-    if (use_extended_notes) {
-      extended_notes_size = notes_size;
-      notes_size = 1;
-    } else {
-      notes_size = 800;
-    }
-  }
-
-  name_size = strlen(CSTRc(name)) + 1;
-  if (name_size > 255) {
-    name_size = 255;
-  }
-  message_init_size(&m, 31 + name_size + notes_size);
-  p = (msg_waypoint_t*) m.data;
-
-  waypoint_i++;
-  le_write32(p->total, waypoint_n);
-  le_write32(p->index, waypoint_i);
-  encode_time(wp->GetCreationTime().toTime_t(), &p->year);
-  le_write32(p->latitude, delbin_deg2rad(wp->latitude));
-  le_write32(p->longitude, delbin_deg2rad(wp->longitude));
-  if (wp->altitude > unknown_alt) {
-    elev = wp->altitude;
-  }
-  le_write_float(p->elevation, elev);
-  if (symbol < 0) {
-    symbol = 0;
-    if (!wp->icon_descr.isNull()) {
-      symbol = waypoint_symbol_index(CSTR(wp->icon_descr));
-    }
-  }
-  p->symbol = symbol;
-  p->name_size = name_size;
-  memcpy(p->name, CSTRc(name), name_size - 1);
-  p->name[name_size - 1] = 0;
-  pp = p->name + name_size;
-  m.size = (pp + 2 + notes_size) - (char*)p;
-  if (extended_notes_size) {
-    le_write16(pp, 0xffff);
-    pp[2] = 0;
-  } else {
-    le_write16(pp, notes_size);
-#if NEW_STRINGS
-#else
-    if (notes) {
-      memcpy(pp + 2, notes, notes_size - 1);
-      pp[2 + notes_size - 1] = 0;
-    }
-#endif
-  }
-
-  add_to_batch(MSG_WAYPOINT_IN, &m);
-
-  if (extended_notes_size) {
-    write_waypoint_notes(notes, extended_notes_size, CSTRc(name));
-  }
-  if (notes_freeable) {
-    xfree(notes_freeable);
-  }
-  if (global_opts.debug_level >= DBGLVL_L) {
-    warning(MYNAME ": wrote waypoint %u '%s'\n", waypoint_i, qPrintable(name));
-  }
-}
-
-static void
-write_waypoints(void)
-{
-  message_t m;
-  unsigned device_n = 0;
-
-  waypoint_i = 0;
-  waypoint_n = waypt_count();
-  if (waypoint_n > device_max_waypoint) {
-    fatal(MYNAME ": waypoint count (%u) exceeds device limit (%u)\n",
-          waypoint_n, device_max_waypoint);
-  }
-
-  message_init_size(&m, 0);
-  message_write(MSG_WAYPOINT_COUNT, &m);
-  if (message_read(MSG_WAYPOINT_COUNT, &m)) {
-    device_n = le_readu32(m.data);
-  }
-
-  waypt_disp_all(write_waypoint);
-  send_batch();
-
-  if (device_n + waypoint_n > device_max_waypoint) {
-    m.size = 0;
-    message_write(MSG_WAYPOINT_COUNT, &m);
-    if (message_read(MSG_WAYPOINT_COUNT, &m) &&
-        le_readu32(m.data) == device_max_waypoint) {
-      warning(MYNAME ": waypoint count (%u already on device + %u added = %u)"
-              " exceeds device limit (%u), some may have been discarded\n",
-              device_n, waypoint_n, device_n + waypoint_n, device_max_waypoint);
-    }
-  }
-  message_free(&m);
-}
-
-//-----------------------------------------------------------------------------
-// Track reading
-
-static void
-decode_sat_fix(Waypoint* wp, const uint8_t status)
-{
-  switch (status & 3) {
-  case 1:
-    wp->fix = fix_none;
-    break;
-  case 2:
-    wp->fix = fix_2d;
-    break;
-  case 3:
-    wp->fix = fix_3d;
-    if (status & 4) {
-      wp->fix = fix_dgps;
-    }
-    break;
-  }
-}
-
-static void
-decode_track_point(const void* data, unsigned* wp_array_i, unsigned max_point)
-{
-  const msg_track_point_t* p = (const msg_track_point_t*) data;
-  const unsigned n = p->number;
-  unsigned i;
-  unsigned j = *wp_array_i;
-
-  if (j + n > max_point) {
-    fatal(MYNAME ": read too many track points\n");
-  }
-  for (i = 0; i < n; i++, j++) {
-    Waypoint* wp = new Waypoint;
-    float elev = le_read_float(p->point[i].elevation);
-    wp_array[j] = wp;
-    wp->SetCreationTime(decode_time(&p->point[i].year));
-    wp->latitude = delbin_rad2deg(le_read32(p->point[i].latitude));
-    wp->longitude = delbin_rad2deg(le_read32(p->point[i].longitude));
-    if (elev > UNKNOWN_ELEV) {
-      wp->altitude = elev;
-    }
-    wp->speed = le_readu16(p->point[i].speed);
-    wp->speed *= (100.0f / (60 * 60));
-    wp->wpt_flags.speed = 1;
-    decode_sat_fix(wp, p->point[i].status);
-    wp->wpt_flags.new_trkseg = (p->point[i].status & 0x10) != 0;
-  }
-  *wp_array_i = j;
-}
-
-static void
-read_track(route_head* track)
-{
-  message_t m;
-  message_t* msg_array;
-  const msg_track_header_t* p;
-  unsigned msg_array_n;
-  unsigned wp_array_i = 0;
-  unsigned n_point;
-  unsigned i;
-  int attempt = ATTEMPT_MAX;
-
-  message_init(&m);
-  // read track messages
-  for (;;) {
-    m.size = MSG_REQUEST_TRACKS_SIZE;
-    memset(m.data, 0, m.size);
-    ((char*)m.data)[0] = 1;  // Download single track
-    strcpy((char*)m.data + 1, CSTRc(track->rte_name));
-    message_write(MSG_REQUEST_TRACKS, &m);
-    if (get_batch(&msg_array, &msg_array_n)) {
-      break;
-    }
-    if (--attempt == 0) {
-      fatal(MYNAME ": reading track '%s' failed\n", qPrintable(track->rte_name));
-    }
-    if (global_opts.debug_level >= DBGLVL_M) {
-      warning(MYNAME ": timed out reading track '%s', retrying\n", qPrintable(track->rte_name));
-    }
-    m.size = MSG_BREAK_SIZE;
-    memset(m.data, 0, m.size);
-    message_write(MSG_BREAK, &m);
-  }
-  message_free(&m);
-  if (msg_array_n == 0 || message_get_id(&msg_array[0]) != MSG_TRACK_HEADER_OUT) {
-    fatal(MYNAME ": reading track '%s' failed (missing track header)\n", qPrintable(track->rte_name));
-  }
-  // process track messages
-  p = (const msg_track_header_t*) msg_array[0].data;
-  if (le_readu16(p->comment_size)) {
-    track->rte_desc = p->comment;
-  }
-  track->line_color.bbggrr = track_color(p->color[0]);
-  n_point = le_readu32(p->total_points);
-  wp_array = (Waypoint**) xcalloc(n_point, sizeof(*wp_array));
-  message_free(&msg_array[0]);
-  for (i = 1; i < msg_array_n; i++) {
-    unsigned id = message_get_id(&msg_array[i]);
-    if (id == MSG_TRACK_POINT_OUT) {
-      decode_track_point(msg_array[i].data, &wp_array_i, n_point);
-    } else {
-      fatal(MYNAME ": unexpected message %x while reading track '%s'\n", id, qPrintable(track->rte_name));
-    }
-    message_free(&msg_array[i]);
-  }
-  xfree(msg_array);
-  if (n_point != wp_array_i) {
-    fatal(MYNAME ": track point count mismatch, expected %u, got %u\n", n_point, wp_array_i);
-  }
-  if (global_opts.debug_level >= DBGLVL_L) {
-    warning(MYNAME ": read track '%s' %u points\n", qPrintable(track->rte_name), n_point);
-  }
-  for (i = 0; i < n_point; i++) {
-    track_add_wpt(track, wp_array[i]);
-  }
-  track_add_head(track);
-  xfree(wp_array);
-}
-
-static void
-read_tracks(void)
-{
-  message_t m;
-  message_t* msg_array;
-  unsigned msg_array_n;
-  route_head** track_array;
-  unsigned total;
-  unsigned i;
-  int attempt = ATTEMPT_MAX;
-
-  message_init(&m);
-  // get number of tracks
-  for (;;) {
-    m.size = 0;
-    message_write(MSG_TRACK_COUNT, &m);
-    if (message_read(MSG_TRACK_COUNT, &m)) {
-      break;
-    }
-    if (--attempt == 0) {
-      fatal(MYNAME ": reading track count failed\n");
-    }
-  }
-  total = le_readu32(m.data);
-  if (global_opts.debug_level >= DBGLVL_L) {
-    warning(MYNAME ": %u tracks\n", total);
-  }
-  if (total == 0) {
-    message_free(&m);
-    return;
-  }
-
-  // First get track headers, then request each track with non-zero number of points
-  attempt = ATTEMPT_MAX;
-  for (;;) {
-    m.size = MSG_REQUEST_TRACKS_SIZE;
-    memset(m.data, 0, m.size);
-    ((char*)m.data)[0] = 2;  // Download all track headers
-    message_write(MSG_REQUEST_TRACKS, &m);
-    if (get_batch(&msg_array, &msg_array_n)) {
-      break;
-    }
-    if (--attempt == 0) {
-      fatal(MYNAME ": reading track headers failed\n");
-    }
-    if (global_opts.debug_level >= DBGLVL_M) {
-      warning(MYNAME ": timed out reading track headers, retrying\n");
-    }
-    m.size = MSG_BREAK_SIZE;
-    memset(m.data, 0, m.size);
-    message_write(MSG_BREAK, &m);
-  }
-  message_free(&m);
-  track_array = (route_head**) xcalloc(total, sizeof(*track_array));
-  for (i = 0; i < msg_array_n; i++) {
-    unsigned id = message_get_id(&msg_array[i]);
-    if (id == MSG_TRACK_HEADER_OUT) {
-      const msg_track_header_t* p = (msg_track_header_t*) msg_array[i].data;
-      if (le_readu32(p->total_points)) {
-        track_array[i] = route_head_alloc();
-        track_array[i]->rte_name = p->name;
-      }
-    } else {
-      fatal(MYNAME ": unexpected message %x while reading track headers\n", id);
-    }
-    message_free(&msg_array[i]);
-  }
-  xfree(msg_array);
-  // get each track
-  for (i = 0; i < total; i++) {
-    if (track_array[i]) {
-      read_track(track_array[i]);
-    }
-  }
-  xfree(track_array);
-}
-
-//-----------------------------------------------------------------------------
-// Track writing
-
-static void
-write_track_points(void)
-{
-  message_t m;
-  const unsigned pt_per_msg = 10;
-  msg_track_point_t* p = NULL;
-  unsigned i = 0;
-  unsigned j = 0;
-
-  do {
-    const Waypoint* wp = wp_array[i];
-    float f;
-
-    if (j == 0) {
-      message_init_size(&m, 9 + 23 * pt_per_msg);
-      p =(msg_track_point_t*) m.data;
-      le_write32(p->total, waypoint_n);
-      le_write32(p->index, i + 1);
-    }
-    assert(p);
-    encode_time(wp->GetCreationTime().toTime_t(), &p->point[j].year);
-    le_write32(p->point[j].latitude, delbin_deg2rad(wp->latitude));
-    le_write32(p->point[j].longitude, delbin_deg2rad(wp->longitude));
-    f = UNKNOWN_ELEV;
-    if (wp->altitude > unknown_alt) {
-      f = wp->altitude;
-    }
-    le_write_float(p->point[j].elevation, f);
-    f = WAYPT_GET(wp, speed, 0);
-    f *= (60 * 60) / 100;
-    le_write16(p->point[j].speed, (uint16_t)f);
-    f = WAYPT_GET(wp, course, 0);
-    f *= 100;
-    le_write16(p->point[j].heading, (uint16_t)f);
-    switch (wp->fix) {
-    default:
-      p->point[j].status = 0;
-      break;
-    case fix_none:
-      p->point[j].status = 1;
-      break;
-    case fix_2d:
-      p->point[j].status = 2;
-      break;
-    case fix_3d:
-      p->point[j].status = 3;
-      break;
-    case fix_dgps:
-      p->point[j].status = 4 | 3;
-      break;
-    }
-    if (wp->wpt_flags.new_trkseg) {
-      p->point[j].status |= 0x10;
-    }
-    i++;
-    j++;
-    if (j == pt_per_msg || i == waypoint_n) {
-      p->number = j;
-      m.size = 9 + 23 * j;
-      add_to_batch(MSG_TRACK_POINT_IN, &m);
-      j = 0;
-    }
-  } while (i < waypoint_n);
-}
-
-static void
-write_track_begin(const route_head* track)
-{
-  waypoint_i = 0;
-  waypoint_n = track->rte_waypt_ct;
-  if (waypoint_n) {
-    wp_array = (Waypoint**) xmalloc(waypoint_n * sizeof(*wp_array));
-  }
-}
-
-static void
-write_track_point(const Waypoint* wp)
-{
-  wp_array[waypoint_i++] = (Waypoint*)wp;
-}
-
-static void
-write_track_end(const route_head* track)
-{
-  message_t m;
-  msg_track_header_in_t* p;
-  unsigned comment_size = 0;
-
-  if (waypoint_n == 0) {
-    return;
-  }
-  if (!track->rte_desc.isEmpty()) {
-    comment_size = strlen(CSTRc(track->rte_desc)) + 1;
-  }
-  message_init_size(&m, sizeof(msg_track_header_in_t) - 1 + comment_size);
-  p = (msg_track_header_in_t*) m.data;
-  memset(p->name, 0, sizeof(p->name));
-  if (!track->rte_name.isEmpty()) {
-    strncpy(p->name, CSTRc(track->rte_name), sizeof(p->name) - 1);
-  } else {
-    sprintf(p->name, "%lu", (long)wp_array[0]->GetCreationTime().toTime_t());
-  }
-  le_write32(p->total_points, waypoint_n);
-  encode_time(current_time().toTime_t(), &p->year);
-  le_write16(p->color, track_color_index(track->line_color.bbggrr));
-  le_write16(p->comment_size, comment_size);
-  if (comment_size) {
-    memcpy(p->comment, CSTRc(track->rte_desc), comment_size);
-  }
-  add_to_batch(MSG_TRACK_HEADER_IN, &m);
-  write_track_points();
-  send_batch();
-  xfree(wp_array);
-}
-
-static void
-write_tracks(void)
-{
-  track_disp_all(write_track_begin, write_track_end, write_track_point);
-}
-
-//-----------------------------------------------------------------------------
-// Route reading
-
-static void
-decode_route_shape(const void* data, unsigned* wp_array_i)
-{
-  const msg_route_shape_t* p = (msg_route_shape_t*) data;
-  const unsigned n = p->number;
-  unsigned i;
-  unsigned j = *wp_array_i;
-
-  for (i = 0; i < n; i++, j++) {
-    char buf[32];
-    Waypoint* wp = new Waypoint;
-    wp_array[j] = wp;
-    wp->latitude = delbin_rad2deg(le_read32(p->point[i].latitude));
-    wp->longitude = delbin_rad2deg(le_read32(p->point[i].longitude));
-    sprintf(buf, "SHP%03u", j);
-    wp->shortname = buf;
-  }
-  *wp_array_i = j;
-}
-
-static Waypoint*
-decode_route_point(const void* data)
-{
-  const msg_route_point_t* p = (const msg_route_point_t*) data;
-  const char* s = NULL;
-  gbfile* fd = gbfopen(NULL, "w", MYNAME);
-  Waypoint* wp = new Waypoint;
-  if (p->name[0]) {
-    wp->shortname = p->name;
-  }
-  // give these a higher priority than the shape points
-  wp->route_priority = 1;
-  wp->latitude = delbin_rad2deg(le_read32(p->latitude));
-  wp->longitude = delbin_rad2deg(le_read32(p->longitude));
-  switch (p->itinerary_type) {
-  case 1:
-    s = "Start";
-    break;
-  case 2:
-    s = "Stop";
-    break;
-  case 3:
-    s = "Finish";
-    break;
-  case 4:
-    s = "Via";
-    break;
-  case 5:
-    s = "Via Hidden";
-    break;
-  case 6:
-    switch (p->turn_type) {
-    case 1:
-      s = "Turn, Straight";
-      break;
-    case 2:
-      s = "Turn, Right";
-      break;
-    case 3:
-      s = "Turn, Bear Right";
-      break;
-    case 4:
-      s = "Turn, Keep Right";
-      break;
-    case 5:
-      s = "Turn, Left";
-      break;
-    case 6:
-      s = "Turn, Bear Left";
-      break;
-    case 7:
-      s = "Turn, Keep Left";
-      break;
-    case 8:
-      s = "Turn, Reverse Direction";
-      break;
-    case 9:
-      s = "Turn, Street Name Change";
-      break;
-    }
-    break;
-  }
-  if (s) {
-    gbfprintf(fd, "Type: %s", s);
-  }
-  if (p->exit_label_size && p->exit_label[0]) {
-    gbfprintf(fd, "\nExit: %s", p->exit_label);
-  }
-  s = p->exit_label + p->exit_label_size;
-  if (s[0] && s[1]) {
-    gbfprintf(fd, "\n%s", s + 1);
-  }
-  if (fd->memlen) {
-    gbfputc(0, fd);
-#if NEW_STRINGS
-    // Reconsider if there's a less grubby way to do this.
-    wp->notes = QString::fromUtf8((const char*) fd->handle.mem, fd->memlen);
-#else
-    wp->notes = (char*) xmalloc(fd->memlen);
-    memcpy(wp->notes, fd->handle.mem, fd->memlen);
-#endif
-  }
-  gbfclose(fd);
-  return wp;
-}
-
-static void
-read_route(route_head* route)
-{
-  message_t m;
-  message_t* msg_array;
-  const msg_route_header_t* p;
-  unsigned msg_array_n;
-  unsigned wp_array_i = 0;
-  unsigned route_total, shape_total, total;
-  unsigned i;
-  int attempt = ATTEMPT_MAX;
-
-  message_init(&m);
-  for (;;) {
-    m.size = MSG_REQUEST_ROUTES_SIZE;
-    memset(m.data, 0, m.size);
-    ((char*)m.data)[0] = 1;  // Download single route
-    strcpy((char*)m.data + 1, CSTRc(route->rte_name));
-    message_write(MSG_REQUEST_ROUTES, &m);
-    if (get_batch(&msg_array, &msg_array_n)) {
-      break;
-    }
-    if (--attempt == 0) {
-      fatal(MYNAME ": reading route '%s' failed (timed out)\n", qPrintable(route->rte_name));
-    }
-    if (global_opts.debug_level >= DBGLVL_M) {
-      warning(MYNAME ": timed out reading route route '%s', retrying\n", qPrintable(route->rte_name));
-    }
-    m.size = MSG_BREAK_SIZE;
-    memset(m.data, 0, m.size);
-    message_write(MSG_BREAK, &m);
-  }
-  message_free(&m);
-  if (msg_array_n == 0 || message_get_id(&msg_array[0]) != MSG_ROUTE_HEADER_OUT) {
-    fatal(MYNAME ": missing route header\n");
-  }
-  p = (const msg_route_header_t*) msg_array[0].data;
-  route_total = le_readu32(p->total_route_point);
-  shape_total = le_readu32(p->total_shape_point);
-  total = route_total + shape_total;
-  wp_array = (Waypoint**) xcalloc(total, sizeof(*wp_array));
-  if (global_opts.debug_level >= DBGLVL_L) {
-    warning(MYNAME ": route '%s' %u points, %u shape points\n",
-            qPrintable(route->rte_name), route_total, shape_total);
-  }
-  message_free(&msg_array[0]);
-  for (i = 1; i < msg_array_n; i++) {
-    unsigned id = message_get_id(&msg_array[i]);
-    if (id == MSG_ROUTE_POINT_OUT) {
-      wp_array[wp_array_i] = decode_route_point(msg_array[i].data);
-      if (global_opts.debug_level >= DBGLVL_L) {
-        warning(MYNAME ": route point '%s'\n", qPrintable(wp_array[wp_array_i]->shortname));
-      }
-      wp_array_i++;
-    } else if (id == MSG_ROUTE_SHAPE_OUT) {
-      decode_route_shape(msg_array[i].data, &wp_array_i);
-    } else {
-      fatal(MYNAME ": unexpected message %x while reading route '%s'\n", id, qPrintable(route->rte_name));
-    }
-    message_free(&msg_array[i]);
-  }
-  xfree(msg_array);
-  if (total != wp_array_i) {
-    fatal(MYNAME ": route point count mismatch, expected %u, got %u\n", total, wp_array_i);
-  }
-  for (i = 0; i < total; i++) {
-    route_add_wpt(route, wp_array[i]);
-  }
-  xfree(wp_array);
-  route_add_head(route);
-}
-
-static void
-read_routes(void)
-{
-  message_t m;
-  message_t* msg_array;
-  unsigned msg_array_n;
-  route_head** route_array;
-  unsigned total;
-  unsigned i;
-  int attempt = ATTEMPT_MAX;
-
-  message_init(&m);
-  // get number of routes
-  for (;;) {
-    m.size = 0;
-    message_write(MSG_ROUTE_COUNT, &m);
-    if (message_read(MSG_ROUTE_COUNT, &m)) {
-      break;
-    }
-    if (--attempt == 0) {
-      fatal(MYNAME ": reading route count failed\n");
-    }
-  }
-  total = le_readu32(m.data);
-  if (global_opts.debug_level >= DBGLVL_L) {
-    warning(MYNAME ": %u routes\n", total);
-  }
-  if (total == 0) {
-    message_free(&m);
-    return;
-  }
-
-  // First get route headers, then request each route
-  attempt = ATTEMPT_MAX;
-  for (;;) {
-    m.size = MSG_REQUEST_ROUTES_SIZE;
-    memset(m.data, 0, m.size);
-    ((char*)m.data)[0] = 2;  // Download all route headers
-    message_write(MSG_REQUEST_ROUTES, &m);
-    if (get_batch(&msg_array, &msg_array_n)) {
-      break;
-    }
-    if (--attempt == 0) {
-      fatal(MYNAME ": reading route headers failed\n");
-    }
-    if (global_opts.debug_level >= DBGLVL_M) {
-      warning(MYNAME ": timed out reading route headers, retrying\n");
-    }
-    m.size = MSG_BREAK_SIZE;
-    memset(m.data, 0, m.size);
-    message_write(MSG_BREAK, &m);
-  }
-  message_free(&m);
-  route_array = (route_head**) xcalloc(total, sizeof(*route_array));
-  for (i = 0; i < msg_array_n; i++) {
-    unsigned id = message_get_id(&msg_array[i]);
-    if (id == MSG_ROUTE_HEADER_OUT) {
-      route_array[i] = route_head_alloc();
-      route_array[i]->rte_name = ((msg_route_header_t*)msg_array[i].data)->name;
-    } else {
-      fatal(MYNAME ": unexpected message %x while reading route headers\n", id);
-    }
-    message_free(&msg_array[i]);
-  }
-  xfree(msg_array);
-  // get each route
-  for (i = 0; i < total; i++) {
-    read_route(route_array[i]);
-  }
-  xfree(route_array);
-}
-
-//-----------------------------------------------------------------------------
-// Route writing
-
-static unsigned route_point_n;
-static unsigned shape_point_n;
-static unsigned* shape_point_counts;
-
-static void
-write_route_shape_points(Waypoint** array, unsigned n)
-{
-  message_t m;
-  const unsigned pt_per_msg = 25;
-  msg_route_shape_t* p = NULL;
-  unsigned i = 0;
-  unsigned j = 0;
-
-  do {
-    if (j == 0) {
-      message_init_size(&m, 10 + 8 * pt_per_msg);
-      p = (msg_route_shape_t*) m.data;
-      le_write32(p->total, n);
-      le_write32(p->index, i + 1);
-      p->reserved = 0;
-    }
-    assert(p);
-    le_write32(p->point[j].latitude, delbin_deg2rad(array[i]->latitude));
-    le_write32(p->point[j].longitude, delbin_deg2rad(array[i]->longitude));
-    i++;
-    j++;
-    if (j == pt_per_msg || i == n) {
-      p->number = j;
-      m.size = 10 + 8 * j;
-      add_to_batch(MSG_ROUTE_SHAPE_IN, &m);
-      j = 0;
-    }
-  } while (i < n);
-}
-
-static void
-write_route_points(void)
-{
-  unsigned route_point_i = 0;
-  unsigned i = 0;
-
-  while (i < waypoint_n) {
-    message_t m;
-    unsigned shape_n;
-    const Waypoint* wp = wp_array[i];
-    msg_route_point_t* p;
-    char* s;
-
-    message_init_size(&m, sizeof(msg_route_point_t) + 1 + 1 + 4);
-    p = (msg_route_point_t*) m.data;
-    memset(m.data, 0, m.size);
-    route_point_i++;
-    shape_n = shape_point_counts[route_point_i];
-    le_write32(p->total, route_point_n);
-    le_write32(p->index, route_point_i);
-    if (!wp->shortname.isEmpty()) {
-      strncpy(p->name, CSTRc(wp->shortname), sizeof(p->name) - 1);
-    } else {
-      sprintf(p->name, "RPT%u", route_point_i);
-    }
-    le_write32(p->latitude, delbin_deg2rad(wp->latitude));
-    le_write32(p->longitude, delbin_deg2rad(wp->longitude));
-    p->exit_label_size = 1;
-    s = p->exit_label + p->exit_label_size;
-    s[0] = 1;  // comment size
-    le_write32(s + 2, shape_n);
-    if (route_point_i == 1) {
-      p->itinerary_type = 1; // start
-    } else if (route_point_i == route_point_n) {
-      p->itinerary_type = 3; // finish
-    }
-    add_to_batch(MSG_ROUTE_POINT_IN, &m);
-    i++;
-    if (shape_n) {
-      write_route_shape_points(&wp_array[i], shape_n);
-      i += shape_n;
-    }
-  }
-}
-
-static void
-write_route_begin(const route_head* track)
-{
-  waypoint_i = 0;
-  route_point_n = 0;
-  shape_point_n = 0;
-  waypoint_n = track->rte_waypt_ct;
-  if (waypoint_n) {
-    wp_array = (Waypoint**) xmalloc(waypoint_n * sizeof(*wp_array));
-    shape_point_counts = (unsigned int*) xcalloc(waypoint_n, sizeof(*shape_point_counts));
-  }
-}
-
-static void
-write_route_point(const Waypoint* wp)
-{
-  wp_array[waypoint_i++] = (Waypoint*)wp;
-  if (wp->shortname.startsWith("SHP")) {
-    shape_point_n++;
-    shape_point_counts[route_point_n]++;
-  } else {
-    route_point_n++;
-  }
-}
-
-static void
-write_route_end(const route_head* route)
-{
-  message_t m;
-  msg_route_header_in_t* p;
-
-  if (waypoint_n == 0) {
-    return;
-  }
-  message_init_size(&m, sizeof(msg_route_header_in_t));
-  p = (msg_route_header_in_t*) m.data;
-  memset(p->name, 0, sizeof(p->name));
-  if (!route->rte_name.isEmpty()) {
-    strncpy(p->name, CSTRc(route->rte_name), sizeof(p->name) - 1);
-  } else {
-    sprintf(p->name, "%lu", (long)wp_array[0]->GetCreationTime().toTime_t());
-  }
-  p->type = 0;
-  le_write32(p->total_route_point, route_point_n);
-  le_write32(p->total_shape_point, shape_point_n);
-  add_to_batch(MSG_ROUTE_HEADER_IN, &m);
-  write_route_points();
-  send_batch();
-  if (wp_array) {
-    xfree(wp_array);
-    xfree(shape_point_counts);
-  }
-}
-
-static void
-write_routes(void)
-{
-  route_disp_all(write_route_begin, write_route_end, write_route_point);
-}
-
-//-----------------------------------------------------------------------------
-// Current position
-
-static Waypoint*
-decode_navmsg(const void* data)
-{
-  Waypoint* wp = new Waypoint;
-  const msg_navigation_t* p = (const msg_navigation_t*) data;
-  struct tm t;
-
-  t.tm_year = le_readu16(p->year) - 1900;
-  t.tm_mon = p->month - 1;
-  t.tm_mday = p->day;
-  t.tm_hour = p->hour;
-  t.tm_min = p->minute;
-  t.tm_sec = p->second;
-  wp->SetCreationTime(mkgmtime(&t));
-  wp->sat = p->satellites;
-  wp->latitude = le_read_double(p->latitude);
-  wp->longitude = le_read_double(p->longitude);
-  wp->altitude = le_read_double(p->elevation);
-  wp->speed = le_read_float(p->speed);
-  wp->speed *= (1000.0f / (60 * 60));
-  wp->wpt_flags.speed = 1;
-  wp->course = le_readu16(p->heading);
-  wp->course /= 100;
-  wp->wpt_flags.course = 1;
-  decode_sat_fix(wp, p->fix_status);
-  wp->shortname = "Position";
-  return wp;
-}
-
-static Waypoint*
-read_position(void)
-{
-  Waypoint* wp;
-  message_t m;
-
-  message_init(&m);
-  message_read(MSG_NAVIGATION, &m);
-  wp = decode_navmsg(m.data);
-  if (wp->fix > fix_none &&
-      message_read_1(MSG_SATELLITE_INFO, &m) == MSG_SATELLITE_INFO) {
-    const msg_satellite_t* p = (const msg_satellite_t*) m.data;
-    wp->hdop = le_readu16(p->hdop);
-    wp->hdop /= 100;
-    wp->vdop = le_readu16(p->vdop);
-    wp->vdop /= 100;
-    wp->pdop = le_readu16(p->pdop);
-    wp->pdop /= 100;
-  }
-  message_free(&m);
-  return wp;
-}
-
-//-----------------------------------------------------------------------------
-
-static void
-delbin_list_units()
-{
-  int i;
-  for (i = 0; i < n_delbin_units; i++) {
-    printf("%u %s %s\n",
-           delbin_unit_info[i].unit_number,
-           delbin_unit_info[i].unit_serial_number,
-           delbin_unit_info[i].unit_name);
-  }
-}
-
-static void
-delbin_rw_init(const QString& fname)
-{
-  message_t m;
-  char buf[256];
-
-  if (!mkshort_handle) {
-    mkshort_handle = mkshort_new_handle();
-  }
-  //  Contrary to doc, it looks like there's a limit of 32 bytes
-  // and a null terminator is required, at least in F/W 2.6.210726
-  // on a PN-40.
-  setshort_length(mkshort_handle, 31);
-  setshort_whitespace_ok(mkshort_handle, 1);
-  setshort_badchars(mkshort_handle, "");
-  setshort_mustuniq(mkshort_handle, 1);
-
-  delbin_os_ops.init(fname);
-
-  // Often the first packet is part of an old message, sometimes it can
-  // confuse the first message read if we don't get rid of it
-  packet_read(buf);
-  // Send a break to clear any state from a previous failure
-  message_init_size(&m, MSG_BREAK_SIZE);
-  memset(m.data, 0, m.size);
-  message_write(MSG_BREAK, &m);
-  // get version info
-  m.size = 0;
-  message_write(MSG_VERSION, &m);
-  if (message_read(MSG_VERSION, &m)) {
-    const msg_version_t* p = (const msg_version_t*) m.data;
-    if (global_opts.debug_level >= DBGLVL_L) {
-      warning(MYNAME ": device %s %s\n", p->product, p->firmware);
-    }
-    if (opt_long_notes) {
-      use_extended_notes = TRUE;
-    } else if (strstr(p->product, "PN-20")) {
-      use_extended_notes = p->firmware[0] > '1' ||
-                           (p->firmware[0] == '1' && p->firmware[2] >= '6');
-    } else if (strstr(p->product, "PN-30") || strstr(p->product, "PN-40")) {
-      use_extended_notes = p->firmware[0] > '2' ||
-                           (p->firmware[0] == '2' && p->firmware[2] >= '5');
-    } else {
-      // assume PN-60 or later
-      use_extended_notes = TRUE;
-    }
-    delbin_unit_info[n_delbin_units].unit_number = n_delbin_units;
-    delbin_unit_info[n_delbin_units].unit_serial_number = xstrndup(p->serial, sizeof(p->serial));
-    delbin_unit_info[n_delbin_units].unit_name = xstrndup(p->product, sizeof(p->product));
-    n_delbin_units++;
-  }
-  message_free(&m);
-
-  if (fname.length() > 4) {
-    if (fname.mid(4,4) == "list") {
-      delbin_list_units();
-      exit(1);
-    }
-  }
-}
-
-static void
-delbin_rw_deinit(void)
-{
-  if (mkshort_handle) {
-    mkshort_del_handle(&mkshort_handle);
-  }
-  delbin_os_ops.deinit();
-}
-
-static void
-delbin_read(void)
-{
-  if (doing_wpts) {
-    if (opt_getposn) {
-      waypt_add(read_position());
-    } else {
-      read_waypoints();
-    }
-  }
-  if (doing_trks) {
-    read_tracks();
-  }
-  if (doing_rtes) {
-    read_routes();
-  }
-}
-
-static void
-delbin_write(void)
-{
-  if (doing_wpts) {
-    message_t m;
-    device_max_waypoint = 1000;
-    message_init_size(&m, 0);
-    message_write(MSG_CAPABILITIES, &m);
-    if (message_read(MSG_CAPABILITIES, &m)) {
-      const msg_capabilities_t* p = (const msg_capabilities_t*) m.data;
-      device_max_waypoint = le_readu32(p->max_waypoints);
-    }
-    message_free(&m);
-
-    if (opt_nuke_wpt) {
-      add_nuke(nuke_type_wpt);
-    }
-    write_waypoints();
-  }
-  if (doing_trks) {
-    if (opt_nuke_trk) {
-      add_nuke(nuke_type_trk);
-    }
-    write_tracks();
-  }
-  if (doing_rtes) {
-    if (opt_nuke_rte) {
-      add_nuke(nuke_type_rte);
-    }
-    write_routes();
-  }
-}
-
-static Waypoint*
-delbin_rd_position(posn_status* status)
-{
-  return read_position();
-}
-
-ff_vecs_t delbin_vecs = {
-  ff_type_serial,
-  FF_CAP_RW_ALL,
-  delbin_rw_init,
-  delbin_rw_init,
-  delbin_rw_deinit,
-  delbin_rw_deinit,
-  delbin_read,
-  delbin_write,
-  NULL,
-  delbin_args,
-  CET_CHARSET_LATIN1, 1,
-  { delbin_rw_init, delbin_rd_position, delbin_rw_deinit }
-};
-
-//=============================================================================
-// OS device I/O implementations
-
-#define VENDOR_ID 0x1163
-#define PRODUCT_ID 0x2020
-
-//-----------------------------------------------------------------------------
-// Windows
-#ifdef HAVE_WDK
-
-#undef HAVE_LIBUSB
-
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-#include <setupapi.h>
-// If hidsdi.h is not found, you need to download the Windows Driver Kit,
-// from http://www.microsoft.com/whdc/Devtools/wdk/default.mspx
-// You need to install 'build environments' and 'tools' from the SDK and
-// follow the instructions in the Install.html to get MSVC to find the right
-// headers and libraries.
-#include <specstrings.h>
-#include <hidsdi.h>
-
-static HANDLE hid_handle;
-
-static void
-win_os_init(const QString& fname)
-{
-  GUID hid_guid;
-  HDEVINFO dev_info;
-  SP_DEVICE_INTERFACE_DATA dev_int_data;
-  PHIDP_PREPARSED_DATA hid_ppd;
-  HIDP_CAPS hid_caps;
-  const char* busy = "";
-  unsigned i;
-
-  hid_handle = INVALID_HANDLE_VALUE;
-  HidD_GetHidGuid(&hid_guid);
-  dev_info = SetupDiGetClassDevs(&hid_guid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
-  if (dev_info == INVALID_HANDLE_VALUE) {
-    fatal(MYNAME ": SetupDiGetClassDevs failed %u\n", GetLastError());
-  }
-  dev_int_data.cbSize = sizeof(dev_int_data);
-  for (i = 0; SetupDiEnumDeviceInterfaces(dev_info, NULL, &hid_guid, i, &dev_int_data); i++) {
-    union {
-      SP_DEVICE_INTERFACE_DETAIL_DATA detail_data;
-      char buf[300];
-    } u;
-    u.detail_data.cbSize = sizeof(u.detail_data);
-    if (SetupDiGetDeviceInterfaceDetail(dev_info,
-                                        &dev_int_data, &u.detail_data, sizeof(u.buf), NULL, NULL)) {
-      HANDLE h = CreateFile(u.detail_data.DevicePath,
-                            FILE_READ_DATA | FILE_WRITE_DATA, 0, NULL, OPEN_EXISTING, 0, NULL);
-      if (h != INVALID_HANDLE_VALUE) {
-        HIDD_ATTRIBUTES hid_attr;
-        hid_attr.Size = sizeof(hid_attr);
-        if (HidD_GetAttributes(h, &hid_attr) &&
-            hid_attr.VendorID == VENDOR_ID && hid_attr.ProductID == PRODUCT_ID) {
-          hid_handle = h;
-          break;
-        }
-        CloseHandle(h);
-      } else if (GetLastError() == ERROR_SHARING_VIOLATION &&
-                 strstr(u.detail_data.DevicePath, "1163") &&
-                 strstr(u.detail_data.DevicePath, "2020")) {
-        busy = " (device busy?)";
-      }
-    }
-  }
-  SetupDiDestroyDeviceInfoList(dev_info);
-  if (hid_handle == INVALID_HANDLE_VALUE) {
-    fatal(MYNAME ": no DeLorme PN found%s\n", busy);
-  }
-  if (!HidD_GetPreparsedData(hid_handle, &hid_ppd)) {
-    fatal(MYNAME ": HidD_GetPreparsedData failed %u\n", GetLastError());
-  }
-  if (!HidP_GetCaps(hid_ppd, &hid_caps)) {
-    fatal(MYNAME ": HidP_GetCaps failed %u\n", GetLastError());
-  }
-  // report length includes report id
-  delbin_os_packet_size = hid_caps.InputReportByteLength - 1;
-  HidD_FreePreparsedData(hid_ppd);
-}
-
-static void
-win_os_deinit(void)
-{
-  CloseHandle(hid_handle);
-}
-
-static unsigned
-win_os_packet_read(void* buf)
-{
-  DWORD n;
-  char buf1[257];
-  // first byte is report id
-  if (ReadFile(hid_handle, buf1, delbin_os_packet_size + 1, &n, NULL) == 0) {
-    unsigned err = GetLastError();
-    fatal(MYNAME ": ReadFile failed %u\n", err);
-  }
-  if (n > 0) {
-    n--;
-  }
-  memcpy(buf, buf1 + 1, n);
-  return n;
-}
-
-static unsigned
-win_os_packet_write(const void* buf, unsigned size)
-{
-  DWORD n;
-  char buf1[257];
-  // first byte is report id
-  buf1[0] = 0;
-  memcpy(buf1 + 1, buf, size);
-  if (WriteFile(hid_handle, buf1, delbin_os_packet_size + 1, &n, NULL) == 0) {
-    unsigned err = GetLastError();
-    fatal(MYNAME ": WriteFile of %u bytes failed with %u.  Size: %u Wrote: %d\n",
-          delbin_os_packet_size + 1, err, size, (int) n);
-  }
-  if (n > size) {
-    n = size;
-  }
-  return n;
-}
-
-delbin_os_ops_t delbin_os_ops = {
-  win_os_init,
-  win_os_deinit,
-  win_os_packet_read,
-  win_os_packet_write
-};
-
-#endif // HAVE_WDK
-
-//-----------------------------------------------------------------------------
-// MacOS X
-#if __APPLE__
-
-#undef HAVE_LIBUSB
-
-#include <pthread.h>
-#include <IOKit/IOKitLib.h>
-#include <IOKit/IOCFPlugIn.h>
-#include <IOKit/hid/IOHIDLib.h>
-#include <mach/mach_error.h>
-
-// IOHIDDeviceInterface121::getReport() does not work, it hangs the process
-// in some sort of unkillable state.  So reading is done via a separate thread
-// with a run loop and interrupt report callback. Yuck.
-
-static IOHIDDeviceInterface122** device;
-static pthread_t thread;
-static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
-static char* report_buf;
-static char* packet_array[32];
-static unsigned packet_array_head;
-static unsigned packet_array_tail;
-static CFRunLoopRef run_loop;
-
-static void*
-thread_func(void* run_loop_source)
-{
-  run_loop = CFRunLoopGetCurrent();
-#if __cplusplus
-  CFRunLoopAddSource(run_loop, (__CFRunLoopSource*) run_loop_source, kCFRunLoopDefaultMode);
-#else
-  CFRunLoopAddSource(run_loop, run_loop_source, kCFRunLoopDefaultMode);
-#endif
-  CFRunLoopRun();
-  return NULL;
-}
-
-static void
-interrupt_report_cb(void* target, IOReturn result, void* refcon, void* sender, UInt32 bufferSize)
-{
-  memcpy(packet_array[packet_array_head], report_buf, delbin_os_packet_size);
-  pthread_mutex_lock(&mutex);
-  if (packet_array_head == packet_array_tail) {
-    pthread_cond_signal(&cond);
-  }
-  packet_array_head++;
-  packet_array_head &= sizeofarray(packet_array) - 1;
-  if (packet_array_head == packet_array_tail && global_opts.debug_level >= DBGLVL_M) {
-    warning(MYNAME ": packet_array overrun, packets lost\n");
-  }
-  pthread_mutex_unlock(&mutex);
-}
-
-static void
-mac_os_init(const QString& fname)
-{
-  CFMutableDictionaryRef dict = IOServiceMatching(kIOHIDDeviceKey);
-  io_service_t service;
-  IOCFPlugInInterface** plugin;
-  CFNumberRef cf_num;
-  CFRunLoopSourceRef run_loop_source;
-  int i;
-  kern_return_t kr;
-  HRESULT hr;
-  IOReturn ir;
-  SInt32 unused;
-
-  i = VENDOR_ID;
-  cf_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &i);
-  CFDictionaryAddValue(dict, CFSTR(kIOHIDVendorIDKey), cf_num);
-  CFRelease(cf_num);
-  i = PRODUCT_ID;
-  cf_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &i);
-  CFDictionaryAddValue(dict, CFSTR(kIOHIDProductIDKey), cf_num);
-  CFRelease(cf_num);
-  service = IOServiceGetMatchingService(kIOMasterPortDefault, dict);
-  if (service == 0) {
-    fatal(MYNAME ": no DeLorme PN found\n");
-  }
-  kr = IOCreatePlugInInterfaceForService(
-         service, kIOHIDDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &unused);
-  if (kr) {
-    fatal(MYNAME ": IOCreatePlugInInterfaceForService failed 0x%x\n", (int)kr);
-  }
-  IOObjectRelease(service);
-  hr = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID122), (void**)&device);
-  if (hr) {
-    fatal(MYNAME ": QueryInterface failed 0x%x\n", (int)hr);
-  }
-  (*plugin)->Release(plugin);
-  ir = (*device)->open(device, kIOHIDOptionsTypeSeizeDevice);
-  if (ir)
-    fatal(MYNAME ": device open failed 0x%x - %s\n", (int)ir,
-          mach_error_string(ir));
-  ir = (*device)->createAsyncEventSource(device, &run_loop_source);
-  if (ir) {
-    fatal(MYNAME ": createAsyncEventSource failed 0x%x\n", (int)ir);
-  }
-  delbin_os_packet_size = 64;
-  report_buf = (char*)xmalloc(delbin_os_packet_size);
-  for (i = sizeofarray(packet_array); i--;) {
-    packet_array[i] = (char*)xmalloc(delbin_os_packet_size);
-  }
-  ir = (*device)->setInterruptReportHandlerCallback(
-         device, report_buf, delbin_os_packet_size, interrupt_report_cb, NULL, NULL);
-  if (ir) {
-    fatal(MYNAME ": setInterruptReportHandlerCallback failed 0x%x\n", (int)ir);
-  }
-  i = pthread_create(&thread, NULL, thread_func, run_loop_source);
-  if (i) {
-    fatal(MYNAME ": pthread_create failed %d\n", i);
-  }
-}
-
-static void
-mac_os_deinit(void)
-{
-  void* unused;
-  unsigned i;
-  CFRunLoopStop(run_loop);
-  pthread_join(thread, &unused);
-  (*device)->Release(device);
-  xfree(report_buf);
-  for (i = sizeofarray(packet_array); i--;) {
-    xfree(packet_array[i]);
-  }
-}
-
-static unsigned
-mac_os_packet_read(void* buf)
-{
-  pthread_mutex_lock(&mutex);
-  while (packet_array_head == packet_array_tail) {
-    pthread_cond_wait(&cond, &mutex);
-  }
-  memcpy(buf, packet_array[packet_array_tail++], delbin_os_packet_size);
-  packet_array_tail &= sizeofarray(packet_array) - 1;
-  pthread_mutex_unlock(&mutex);
-  return delbin_os_packet_size;
-}
-
-static unsigned
-mac_os_packet_write(const void* buf, unsigned size)
-{
-  IOReturn r = (*device)->setReport(
-                 device, kIOHIDReportTypeOutput, 0, (void*)buf, size, 2000, NULL, NULL, NULL);
-  if (r) {
-    fatal("setReport failed 0x%x\n", (int)r);
-  }
-  return size;
-}
-
-delbin_os_ops_t delbin_os_ops = {
-  mac_os_init,
-  mac_os_deinit,
-  mac_os_packet_read,
-  mac_os_packet_write
-};
-
-#endif // __APPLE__
-
-//-----------------------------------------------------------------------------
-// libusb
-#if HAVE_LIBUSB
-
-#include <usb.h>
-
-static struct usb_device* usb_dev;
-static usb_dev_handle* usb_handle;
-static int endpoint_in;
-static int endpoint_out;
-
-static void
-libusb_os_init(const QString& fname)
-{
-  struct usb_bus* bus;
-  const struct usb_endpoint_descriptor* endpoint_desc;
-
-  usb_init();
-  usb_find_busses();
-  usb_find_devices();
-  for (bus = usb_busses; usb_dev == NULL && bus; bus = bus->next) {
-    struct usb_device* d;
-    for (d = bus->devices; d; d = d->next) {
-      if (d->descriptor.idVendor == VENDOR_ID && d->descriptor.idProduct == PRODUCT_ID) {
-        usb_dev = d;
-        break;
-      }
-    }
-  }
-  if (usb_dev == NULL) {
-    fatal(MYNAME ": no DeLorme PN found\n");
-  }
-  usb_handle = usb_open(usb_dev);
-  if (usb_handle == NULL) {
-    fatal(MYNAME ": %s\n", usb_strerror());
-  }
-
-  // Device has 1 configuration, 1 interface, 2 interrupt endpoints
-  if (usb_claim_interface(usb_handle, 0) < 0) {
-#if LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
-    if (usb_detach_kernel_driver_np(usb_handle, 0) < 0) {
-      warning(MYNAME ": %s\n", usb_strerror());
-    }
-    if (usb_claim_interface(usb_handle, 0) < 0)
-#endif
-    {
-      const char* s = usb_strerror();
-      usb_close(usb_handle);
-      fatal(MYNAME ": %s\n", s);
-    }
-  }
-  endpoint_desc = usb_dev->config[0].interface[0].altsetting[0].endpoint;
-  delbin_os_packet_size = endpoint_desc[0].wMaxPacketSize;
-  endpoint_in = endpoint_desc[0].bEndpointAddress;
-  endpoint_out = endpoint_desc[1].bEndpointAddress;
-  if ((endpoint_in & USB_ENDPOINT_DIR_MASK) == USB_ENDPOINT_OUT) {
-    int t = endpoint_in;
-    endpoint_in = endpoint_out;
-    endpoint_out = t;
-  }
-}
-
-static void
-libusb_os_deinit(void)
-{
-  usb_release_interface(usb_handle, 0);
-  usb_close(usb_handle);
-}
-
-static unsigned
-libusb_os_packet_read(void* buf)
-{
-  int n = usb_interrupt_read(usb_handle, endpoint_in, (char*) buf, delbin_os_packet_size, 2000);
-  if (n < 0) {
-    fatal(MYNAME ": %s\n", usb_strerror());
-  }
-  return n;
-}
-
-static unsigned
-libusb_os_packet_write(const void* buf, unsigned size)
-{
-  int n = usb_interrupt_write(usb_handle, endpoint_out, (char*)buf, size, 2000);
-  if (n < 0) {
-    fatal(MYNAME ": %s\n", usb_strerror());
-  }
-  return n;
-}
-
-#if HAVE_LINUX_HID
-static const delbin_os_ops_t libusb_os_ops =
-#else
-delbin_os_ops_t delbin_os_ops =
-#endif
-{
-  libusb_os_init,
-  libusb_os_deinit,
-  libusb_os_packet_read,
-  libusb_os_packet_write
-};
-
-#endif // HAVE_LIBUSB
-
-//-----------------------------------------------------------------------------
-// Linux
-#if HAVE_LINUX_HID
-
-#include <unistd.h>
-#include <fcntl.h>
-#include <dirent.h>
-#include <errno.h>
-#include <sys/ioctl.h>
-#include <linux/types.h>
-#include <linux/hiddev.h>
-#include <linux/hidraw.h>
-
-// Read from hidraw, write to hiddev. Reading from hiddev does not work,
-// and neither does writing to hidraw.
-static int fd_hidraw;
-static int fd_hiddev;
-
-static int linuxhid_os_init_status;
-
-static void
-linuxhid_os_init(const QString& fname)
-{
-  struct hidraw_devinfo info;
-  struct hiddev_field_info finfo;
-  DIR* dir = NULL;
-  struct dirent* d;
-
-  fd_hidraw = fd_hiddev = -1;
-  if (fname.startsWith("hid:")) {
-    char* raw_name = xstrdup(qPrintable(fname.mid(4)));
-    char* dev_name = strchr(raw_name, ',');
-    if (dev_name == NULL) {
-      fatal(MYNAME ": missing hiddev path\n");
-    }
-    *dev_name++ = 0;
-    fd_hidraw = open(raw_name, O_RDONLY);
-    if (fd_hidraw < 0) {
-      fatal(MYNAME ": %s: %s\n", raw_name, strerror(errno));
-    }
-    fd_hiddev = open(dev_name, O_WRONLY);
-    if (fd_hiddev < 0) {
-      fatal(MYNAME ": %s: %s\n", dev_name, strerror(errno));
-    }
-    xfree(raw_name);
-  } else {
-    dir = opendir("/dev");
-  }
-  while (dir && (d = readdir(dir)) != NULL) {
-    if (strncmp(d->d_name, "hidraw", 6) == 0) {
-      int fd1, fd2;
-      char raw_name[32];
-      char dev_name[32];
-      sprintf(raw_name, "/dev/%s", d->d_name);
-      fd1 = open(raw_name, O_RDONLY);
-      if (fd1 < 0) {
-        if (global_opts.debug_level >= DBGLVL_M) {
-          warning(MYNAME ": %s: %s\n", raw_name, strerror(errno));
-        }
-        continue;
-      }
-      sprintf(dev_name, "/dev/usb/hiddev%s", raw_name + sizeof("/dev/hidraw") - 1);
-      fd2 = open(dev_name, O_WRONLY);
-      if (fd2 < 0 && errno == ENOENT) {
-        sprintf(dev_name, "/dev/hiddev%s", raw_name + sizeof("/dev/hidraw") - 1);
-        fd2 = open(dev_name, O_WRONLY);
-      }
-      if (fd2 < 0) {
-        if (global_opts.debug_level >= DBGLVL_M) {
-          warning(MYNAME ": %s: %s\n", dev_name, strerror(errno));
-        }
-        close(fd1);
-        continue;
-      }
-      if (ioctl(fd1, HIDIOCGRAWINFO, &info) == 0 &&
-          info.vendor == VENDOR_ID && info.product == PRODUCT_ID) {
-        fd_hidraw = fd1;
-        fd_hiddev = fd2;
-        break;
-      }
-      close(fd1);
-      close(fd2);
-    }
-  }
-  if (dir) {
-    closedir(dir);
-  }
-  if (fd_hidraw < 0) {
-    if (linuxhid_os_init_status == 0) {
-      fatal(MYNAME ": no DeLorme PN found\n");
-    }
-    return;
-  }
-  finfo.report_type = HID_REPORT_TYPE_INPUT;
-  finfo.report_id = 0;
-  finfo.field_index = 0;
-  if (ioctl(fd_hiddev, HIDIOCGFIELDINFO, &finfo) < 0) {
-    warning(MYNAME ": HIDIOCGFIELDINFO: %s\n", strerror(errno));
-    if (linuxhid_os_init_status == 0) {
-      exit(1);
-    }
-    return;
-  }
-  delbin_os_packet_size = finfo.maxusage;
-  linuxhid_os_init_status = 0;
-}
-
-static void
-linuxhid_os_deinit(void)
-{
-  close(fd_hidraw);
-  close(fd_hiddev);
-}
-
-static unsigned
-linuxhid_os_packet_read(void* buf)
-{
-  int n = read(fd_hidraw, buf, delbin_os_packet_size);
-  if (n < 0) {
-    fatal(MYNAME ": %s\n", strerror(errno));
-  }
-  return n;
-}
-
-static unsigned
-linuxhid_os_packet_write(const void* buf, unsigned size)
-{
-  struct hiddev_usage_ref_multi urefm;
-  struct hiddev_report_info rinfo;
-  const uint8_t* p = (const uint8_t*) buf;
-  unsigned i;
-
-  for (i = 0; i < size; i++) {
-    urefm.values[i] = p[i];
-  }
-  urefm.num_values = size;
-  memset(&urefm.uref, 0, sizeof(urefm.uref));
-  urefm.uref.report_type = HID_REPORT_TYPE_OUTPUT;
-  if (ioctl(fd_hiddev, HIDIOCSUSAGES, &urefm)) {
-    fatal(MYNAME ": HIDIOCSUSAGES: %s\n", strerror(errno));
-  }
-  memset(&rinfo, 0, sizeof(rinfo));
-  rinfo.report_type = HID_REPORT_TYPE_OUTPUT;
-  if (ioctl(fd_hiddev, HIDIOCSREPORT, &rinfo)) {
-    fatal(MYNAME ": HIDIOCSREPORT: %s\n", strerror(errno));
-  }
-  return size;
-}
-
-static const delbin_os_ops_t linuxhid_os_ops = {
-  linuxhid_os_init,
-  linuxhid_os_deinit,
-  linuxhid_os_packet_read,
-  linuxhid_os_packet_write
-};
-
-static void
-linux_os_init(const QString& fname)
-{
-  // tell linuxhid_os_init not to exit
-  linuxhid_os_init_status = 1;
-  linuxhid_os_init(fname);
-  if (linuxhid_os_init_status == 0) {
-    delbin_os_ops = linuxhid_os_ops;
-  } else {
-#if HAVE_LIBUSB
-    if (global_opts.debug_level >= DBGLVL_M) {
-      warning(MYNAME ": HID init failed, falling back to libusb\n");
-    }
-    delbin_os_ops = libusb_os_ops;
-    delbin_os_ops.init(fname);
-#else
-    fatal(MYNAME ": no DeLorme PN found\n");
-#endif
-  }
-}
-
-delbin_os_ops_t delbin_os_ops = {
-  linux_os_init,
-  NULL,
-  NULL,
-  NULL
-};
-
-#endif // HAVE_LINUX_HID
-
-//-----------------------------------------------------------------------------
-// stubs
-#if !(HAVE_WDK || __APPLE__ || HAVE_LIBUSB || HAVE_LINUX_HID)
-static void
-stub_os_init(const QString& fname)
-{
-  fatal(MYNAME ": OS not supported\n");
-}
-static void
-stub_os_deinit(void)
-{
-}
-static unsigned
-stub_os_packet_read(void* buf)
-{
-  return 0;
-}
-static unsigned
-stub_os_packet_write(const void* buf, unsigned size)
-{
-  return 0;
-}
-delbin_os_ops_t delbin_os_ops = {
-  stub_os_init,
-  stub_os_deinit,
-  stub_os_packet_read,
-  stub_os_packet_write
-};
-#endif
-// end OS device I/O implementations section
-//=============================================================================
-
-static const int track_color_bgr[] = {
-  0x0000ff, // red
-  0x00ffff, // yellow
-  0x008000, // green
-  0xff0000, // blue
-  0x808080, // gray
-  0xffffff, // white
-  0,        // black
-  0xffff00, // cyan
-  0xff00ff, // magenta
-  0x00a5ff, // orange
-  0x82004b, // indigo
-  0xeea5ee  // violet
-};
-
-static int track_color(unsigned i)
-{
-  int bgr = -1;
-  if (i < sizeofarray(track_color_bgr)) {
-    bgr = track_color_bgr[i];
-  }
-  return bgr;
-}
-
-static unsigned track_color_index(int bgr)
-{
-  unsigned i = sizeofarray(track_color_bgr);
-  do {
-    i--;
-  } while (i != 0 && track_color_bgr[i] != bgr);
-  return i;
-}
-
-static const char* const waypoint_symbol_name[] = {
-  // 0
-  "Red Map Pin",
-  "Dark Red Map Pin",
-  "Yellow Map Pin",
-  "Dark Yellow Map Pin",
-  "Green Map Pin",
-  "Dark Green Map Pin",
-  "Turquoise Map Pin",
-  "Dark Turquoise Map Pin",
-  "Blue Map Pin",
-  "Dark Blue Map Pin",
-  // 10
-  "Gray Map Pin",
-  "Dark Gray Map Pin",
-  "Red Flag",
-  "Dark Red Flag",
-  "Yellow Flag",
-  "Dark Yellow Flag",
-  "Green Flag",
-  "Dark Green Flag",
-  "Turquoise Flag",
-  "Dark Turquoise Flag",
-  // 20
-  "Blue Flag",
-  "Dark Blue Flag",
-  "Gray Flag",
-  "Dark Gray Flag",
-  "Red Dot",
-  "Dark Red Dot",
-  "Yellow Dot",
-  "Dark Yellow Dot",
-  "Green Dot",
-  "Dark Green Dot",
-  // 30
-  "Turquoise Dot",
-  "Dark Turquoise Dot",
-  "Blue Dot",
-  "Dark Blue Dot",
-  "Gray Dot",
-  "Dark Gray Dot",
-  "Small Red Dot",
-  "Small Dark Red Dot",
-  "Small Yellow Dot",
-  "Small Dark Yellow Dot",
-  // 40
-  "Small Green Dot",
-  "Small Dark Green Dot",
-  "Small Turquoise Dot",
-  "Small Dark Turquoise Dot",
-  "Small Blue Dot",
-  "Small Dark Blue Dot",
-  "Small Gray Dot",
-  "Small Dark Gray Dot",
-  "Arrow Up",
-  "Arrow Down",
-  // 50
-  "Arrow Left",
-  "Arrow Right",
-  "Arrow Up Left",
-  "Arrow Up Right",
-  "Arrow Down Left",
-  "Arrow Down Right",
-  "Green Star",
-  "Yellow Square",
-  "Red X",
-  "Turquoise Circle",
-  // 60
-  "Purple Triangle",
-  "American Flag",
-  "Stop",
-  "Parking",
-  "First Aid",
-  "Dining",
-  "Railroad Crossing",
-  "Heliport",
-  "Restroom",
-  "Information",
-  // 70
-  "Diver Down",
-  "Exit",
-  "Health Facility",
-  "Police",
-  "Post Office",
-  "Mining",
-  "Danger",
-  "Money",
-  "Exclamation",
-  "Car",
-  // 80
-  "Jeep",
-  "Truck",
-  "Tow Truck",
-  "Motor Home",
-  "School Bus",
-  "Four-wheeler",
-  "Snowmobile",
-  "Sailboat",
-  "Powerboat",
-  "Boat Launch",
-  // 90
-  "Anchor",
-  "Buoy",
-  "Shipwreck",
-  "Glider Area",
-  "Private Airport",
-  "Public Airport",
-  "Military Airport",
-  "Military Base",
-  "House",
-  "Church",
-  // 100
-  "Building",
-  "School",
-  "Lighthouse",
-  "Bridge",
-  "Radio Tower",
-  "Dam",
-  "Tunnel",
-  "Toll Booth",
-  "Gas Station",
-  "Lodging",
-  // 110
-  "Telephone",
-  "Traffic Light",
-  "Fire Hydrant",
-  "Cemetery",
-  "Picnic Table",
-  "Tent",
-  "Shelter",
-  "Camper",
-  "Fire",
-  "Shower",
-  // 120
-  "Drinking Water",
-  "Binoculars",
-  "Camera",
-  "Geocache",
-  "Geocache Found",
-  "Fishing Pole",
-  "Ice Fishing Trap Set",
-  "Ice Fishing Trap Up",
-  "Moose",
-  "Deer",
-  // 130
-  "Bear",
-  "Bird",
-  "Duck",
-  "Fish",
-  "Deer Tracks",
-  "Animal Tracks",
-  "Bird Tracks",
-  "Birch Tree",
-  "Evergreen Tree",
-  "Deciduous Tree",
-  // 140
-  "Flower Garden",
-  "Mountain",
-  "Cave",
-  "Beach",
-  "Hiking",
-  "Swimming",
-  "Bicycling",
-  "Kayaking",
-  "Canoeing",
-  "Water Skiing",
-  // 150
-  "Cross-country Skiing",
-  "Downhill Skiing",
-  "Ice Skating",
-  "Dogsledding",
-  "Shooting",
-  "Golf Course",
-  "Ballpark",
-  // 157-182 added in PN-40 2.5 firmware
-  "Cache Found",
-  "Didn't Find It",
-  "My Cache",
-  // 160
-  "Traditional Cache",
-  "Multi-Cache",
-  "Unknown Cache",
-  "Letterbox Hybrid",
-  "Whereigo Cache",
-  "Event Cache",
-  "Mega-Event Cache",
-  "Cache In Trash Out Event",
-  "EarthCache",
-  "Virtual Cache",
-  // 170
-  "Webcam Cache",
-  "Waymark",
-  "NGS Benchmark",
-  "Write Note",
-  "Needs Maintenance",
-  "Final Location",
-  "Parking Area",
-  "Question to Answer",
-  "Reference Point",
-  "Stages of a Multicache",
-  // 180
-  "Trailhead",
-  "Temporarily Disable Listing",
-  "Enable Listing",
-  // 183-222 added in PN-40 2.7 firmware
-  "Crane Truck",
-  "Forest Fire",
-  "Oil Derrick",
-  "Wind Turbine",
-  "Letter A",
-  "Letter B",
-  "Letter C",
-  // 190
-  "Letter D",
-  "Letter E",
-  "Letter F",
-  "Letter G",
-  "Letter H",
-  "Letter I",
-  "Letter J",
-  "Letter K",
-  "Letter L",
-  "Letter M",
-  // 200
-  "Letter N",
-  "Letter O",
-  "Letter P",
-  "Letter Q",
-  "Letter R",
-  "Letter S",
-  "Letter T",
-  "Letter U",
-  "Letter V",
-  "Letter W",
-  // 210
-  "Letter X",
-  "Letter Y",
-  "Letter Z",
-  "Numeral 0",
-  "Numeral 1",
-  "Numeral 2",
-  "Numeral 3",
-  "Numeral 4",
-  "Numeral 5",
-  "Numeral 6",
-  // 220
-  "Numeral 7",
-  "Numeral 8",
-  "Numeral 9"
-};
-
-static const char*
-waypoint_symbol(unsigned i)
-{
-  const char* p = NULL;
-  if (i < sizeofarray(waypoint_symbol_name)) {
-    p = waypoint_symbol_name[i];
-  }
-  return p;
-}
-
-static unsigned
-waypoint_symbol_index(const char* name)
-{
-  static unsigned last_result;
-  static char last_name[32];
-  unsigned i = last_result;
-
-  if (strncmp(name, last_name, sizeof(last_name)) != 0) {
-    i = sizeofarray(waypoint_symbol_name);
-    do {
-      i--;
-    } while (i != 0 && case_ignore_strcmp(name, waypoint_symbol_name[i]) != 0);
-    strncpy(last_name, name, sizeof(last_name));
-    last_result = i;
-  }
-  return i;
-}
-
-// vi: ts=4 sw=4 noexpandtab
diff --git a/deprecated/delbin.cc b/deprecated/delbin.cc
new file mode 100644 (file)
index 0000000..56735fc
--- /dev/null
@@ -0,0 +1,3373 @@
+/*
+       DeLorme PN-20/40 USB "DeLBin" protocol
+
+    Copyright (C) 2009 Paul Cornett, pc-gpsb at bullseye.com
+    Copyright (C) 2005-2014 Robert Lipe, robertlipe+source@gpsbabel.orgg
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
+
+ */
+
+#include "defs.h"
+#include "src/core/xmltag.h"
+#include <assert.h>
+#include <stdio.h> /* for atoi, sprintf */
+#include <stdlib.h> // atoi
+
+#define MYNAME "delbin"
+static short_handle mkshort_handle;
+
+/*
+Device documentation:
+"DeLorme Binary GPS Format", delbin_user_interface_format_176.pdf
+obtained here: http://forum.delorme.com/viewtopic.php?t=13846
+
+Notes:
+Initial development was done with a PN-40, firmware 2.4.123299. The test
+device was upgraded to firmware 2.5.165506 during development.
+
+The "data size" in the message header includes the 4 trailer bytes, so it
+is really the size of the whole message minus the header.
+
+Messages do not always start at the beginning of a packet. Every once in a
+while, the start of the next message directly follows the end of the previous
+one, in the same packet.
+
+The time before an unacknowledged message will be retransmitted by the
+device is on the order of 2 to 4 seconds.
+
+Retrieving all tracks at once (using code 0 in message 0xb031) does not
+seem to work, it hangs after the first track, maybe waiting for some
+undocumented response message.
+
+Character encoding is not documented, appears to be 8859-1.
+
+The undocumented messages 0xaa01, 0xb015, 0xb016 and the use of the
+"reserved" byte in message 0xb012 were discovered by examining the data
+transferred between the device and DeLorme Topo 8.0. They may have been
+added in the PN-40 2.5 firmware.
+*/
+
+//-----------------------------------------------------------------------------
+// interface to platform-specific device I/O
+typedef struct {
+  void (*init)(const QString& name);
+  void (*deinit)(void);
+  unsigned(*packet_read)(void*);
+  unsigned(*packet_write)(const void*, unsigned);
+} delbin_os_ops_t;
+
+// really static, only extern so it can be forward declared
+extern delbin_os_ops_t delbin_os_ops;
+
+static unsigned delbin_os_packet_size;
+//-----------------------------------------------------------------------------
+
+// number of times to attempt a transfer before giving up
+#define ATTEMPT_MAX 2
+// seconds to wait for expected message (actual time will be somewhat
+// indeterminate, but at least READ_TIMEOUT - 1)
+#define READ_TIMEOUT 6
+
+// debug output: low, medium, high, higher
+#define DBGLVL_L 1
+#define DBGLVL_M 2
+#define DBGLVL_H 3
+#define DBGLVL_H2 4
+
+// Multiple unit support.
+#define DELBIN_MAX_UNITS 32
+static struct {
+  unsigned int unit_number;
+  const char* unit_serial_number;
+  const char* unit_name;
+} delbin_unit_info[DELBIN_MAX_UNITS];
+static int n_delbin_units;
+
+#define UNKNOWN_ELEV -2000000
+
+#define sizeofarray(x) (sizeof(x) / sizeof(x[0]))
+
+static char* opt_getposn = NULL;
+static char* opt_logs = NULL;
+static char* opt_long_notes = NULL;
+static char* opt_nuke_wpt = NULL;
+static char* opt_nuke_trk = NULL;
+static char* opt_nuke_rte = NULL;
+/* If true, Order hint to match Cache Register and Topo 7 */
+static char* opt_hint_at_end = NULL;
+static char* opt_gcsym = NULL;
+
+
+static arglist_t delbin_args[] = {
+  {
+    "get_posn", &opt_getposn, "Return current position as a waypoint",
+    NULL, ARGTYPE_BOOL, ARG_NOMINMAX
+  },
+  {
+    "logs", &opt_logs, "Include groundspeak logs when writing",
+    NULL, ARGTYPE_BOOL, ARG_NOMINMAX
+  },
+  {
+    "long_notes", &opt_long_notes, "Use long waypoint notes regardless of PN version",
+    NULL, ARGTYPE_BOOL, ARG_NOMINMAX
+  },
+  {
+    "nukewpt", &opt_nuke_wpt, "Delete all waypoints before sending", NULL, ARGTYPE_BOOL,
+    ARG_NOMINMAX
+  },
+  {
+    "nuketrk", &opt_nuke_trk, "Delete all tracks before sending", NULL, ARGTYPE_BOOL,
+    ARG_NOMINMAX
+  },
+  {
+    "nukerte", &opt_nuke_rte, "Delete all routes before sending", NULL, ARGTYPE_BOOL,
+    ARG_NOMINMAX
+  },
+  {"hint_at_end", &opt_hint_at_end, "If true, geocache hint at end of text", NULL, ARGTYPE_BOOL, ARG_NOMINMAX },
+  {"gcsym", &opt_gcsym, "If set to 0, prefer user-provided symbols over Groundspeaks ones for geocaches", "1", ARGTYPE_BOOL, ARG_NOMINMAX },
+  ARG_TERMINATOR
+};
+
+// Whether device understands message 0xb016
+static int use_extended_notes;
+
+// Device capabilities
+static unsigned device_max_waypoint;
+
+static const char* waypoint_symbol(unsigned index);
+static unsigned waypoint_symbol_index(const char* name);
+static int track_color(unsigned index);
+static unsigned track_color_index(int bgr);
+
+static unsigned waypoint_i;
+static unsigned waypoint_n;
+static Waypoint** wp_array;
+
+//-----------------------------------------------------------------------------
+// Message ids and sizes. Only the needed ones are here.
+// Note that "in" and "out" ids are named as in the device documentation,
+// so "in" means to the device, "out" means from.
+#define MSG_ACK 0xaa00
+#define MSG_BREAK 0xaa02
+#define MSG_BREAK_SIZE 33
+#define MSG_CAPABILITIES 0xb001
+#define MSG_DELETE 0xb005
+#define MSG_DELETE_SIZE 67
+#define MSG_ERROR 0xa003
+#define MSG_NAVIGATION 0xa010
+#define MSG_REQUEST_ROUTES 0xb051
+#define MSG_REQUEST_ROUTES_SIZE 65
+#define MSG_REQUEST_TRACKS 0xb031
+#define MSG_REQUEST_TRACKS_SIZE 33
+#define MSG_REQUEST_WAYPOINTS 0xb012
+#define MSG_REQUEST_WAYPOINTS_SIZE 15
+#define MSG_ROUTE_COUNT 0xb050
+#define MSG_ROUTE_HEADER_IN 0xb055
+#define MSG_ROUTE_HEADER_OUT 0xb052
+#define MSG_ROUTE_POINT_IN 0xb056
+#define MSG_ROUTE_POINT_OUT 0xb053
+#define MSG_ROUTE_SHAPE_IN 0xb057
+#define MSG_ROUTE_SHAPE_OUT 0xb054
+#define MSG_SATELLITE_INFO 0xa020
+#define MSG_TRACK_COUNT 0xb030
+#define MSG_TRACK_HEADER_IN 0xb035
+#define MSG_TRACK_HEADER_OUT 0xb032
+#define MSG_TRACK_POINT_IN 0xb036
+#define MSG_TRACK_POINT_OUT 0xb033
+#define MSG_TRANSFER_COMPLETE 0xaa04
+#define MSG_VERSION 0xa001
+#define MSG_WAYPOINT_COUNT 0xb010
+#define MSG_WAYPOINT_IN 0xb014
+#define MSG_WAYPOINT_OUT 0xb013
+// Undocumented:
+// This one looks like MSG_ACK, except it also has a string in it that says
+// something like "device is busy". The expected MSG_ACK usually immediately
+// follows it, so the point of this one is unclear.
+#define MSG_NACK 0xaa01
+// Long waypoint notes
+#define MSG_WAYPOINT_NOTE_IN 0xb016
+#define MSG_WAYPOINT_NOTE_OUT 0xb015
+
+//-----------------------------------------------------------------------------
+// Message structures
+
+// Input Delete Message
+// Message ID: 0xB005
+typedef enum {
+  nuke_type_wpt = 0,
+  nuke_type_trk = 1,
+  nuke_type_rte = 2,
+  // int nuke_map = 3;
+} nuke_type;
+
+typedef enum {
+  nuke_mode_all = 0,
+  nuke_mode_single = 1
+} nuke_mode;
+
+typedef enum {
+  nuke_dest_internal = 0,
+  nuke_dest_sd = 1
+} nuke_dest;
+
+typedef struct {
+  uint8_t type;
+  uint8_t mode;
+  uint8_t location;
+  char object_name[64];
+} msg_delete_t;
+
+// Output Waypoint Message
+// Message ID: 0xB013
+// Input Waypoint Message
+// Message ID: 0xB014
+typedef struct {
+  uint8_t total[4]; // U32
+  uint8_t index[4]; // U32
+  uint8_t year;
+  uint8_t month;
+  uint8_t day;
+  uint8_t hour;
+  uint8_t minute;
+  uint8_t second;
+  uint8_t latitude[4]; // S32 rad * 100000000
+  uint8_t longitude[4]; // S32 rad * 100000000
+  uint8_t elevation[4]; // F32 meters
+  uint8_t color;
+  uint8_t symbol;
+  uint8_t name_size;
+  char name[1];
+  // note_size[2] U16
+  // note[note_size]
+} msg_waypoint_t;
+
+// undocumented, seen with PN-40 2.5 firmware
+// output waypoint note
+// Message ID: 0xB015
+// input waypoint note
+// Message ID: 0xB016
+typedef struct {
+  uint8_t index[2];
+  uint8_t total[2];
+  uint8_t name_size;
+  char name[1];
+  // note_size[2]
+  // note[note_size]
+} msg_waypoint_note_t;
+
+// Output Track Point Message
+// Message ID: 0xB033
+// Input Track Point Message
+// Message ID: 0xB036
+typedef struct {
+  uint8_t total[4]; // U32
+  uint8_t index[4]; // U32
+  uint8_t number;
+  struct {
+    uint8_t year;
+    uint8_t month;
+    uint8_t day;
+    uint8_t hour;
+    uint8_t minute;
+    uint8_t second;
+    uint8_t latitude[4]; // S32 rad * 100000000
+    uint8_t longitude[4]; // S32 rad * 100000000
+    uint8_t elevation[4]; // F32 meters
+    uint8_t speed[2]; // U16 km/h * 10
+    uint8_t heading[2]; // U16 deg * 100
+    uint8_t status;
+  } point[1];
+} msg_track_point_t;
+
+// Output Track Header (Name) Message
+// Message ID: 0xB032
+typedef struct {
+  uint8_t total_tracks[2]; // U16
+  uint8_t number[2]; // U16
+  char name[32];
+  uint8_t total_points[4]; // U32
+  uint8_t year;
+  uint8_t month;
+  uint8_t day;
+  uint8_t hour;
+  uint8_t minute;
+  uint8_t second;
+  uint8_t color[2]; // U16
+  uint8_t distance[4]; // U32 m
+  uint8_t duration[4]; // U32 sec
+  uint8_t comment_size[2]; // U16
+  char comment[1];
+} msg_track_header_t;
+
+// Input Upload Track Header Message
+// Message ID: 0xB035
+typedef struct {
+  char name[32];
+  uint8_t total_points[4]; // U32
+  uint8_t year;
+  uint8_t month;
+  uint8_t day;
+  uint8_t hour;
+  uint8_t minute;
+  uint8_t second;
+  uint8_t color[2]; // U16
+  uint8_t comment_size[2]; // U16
+  char comment[1];
+} msg_track_header_in_t;
+
+// Output Route Shape Message
+// Message ID: 0xB054
+typedef struct {
+  uint8_t total[4]; // U32
+  uint8_t index[4]; // U32
+  uint8_t number;
+  uint8_t reserved;
+  struct {
+    uint8_t latitude[4]; // S32 rad * 100000000
+    uint8_t longitude[4]; // S32 rad * 100000000
+  } point[1];
+} msg_route_shape_t;
+
+// Output Route Point Message
+// Message ID: 0xB053
+// Input Route Itin Point Message
+// Message ID: 0xB056
+typedef struct {
+  uint8_t total[4]; // U32
+  uint8_t index[4]; // U32
+  char name[32];
+  uint8_t latitude[4]; // S32 rad * 100000000
+  uint8_t longitude[4]; // S32 rad * 100000000
+  uint8_t time_from_start[4]; // U32 sec
+  uint8_t distance_from_start[4]; // F32 km
+  uint8_t bearing_in[2]; // U16 deg * 100
+  uint8_t bearing_out[2]; // U16 deg * 100
+  uint8_t bearing_next[2]; // U16 deg * 100
+  uint8_t itinerary_type;
+  uint8_t turn_type;
+  uint8_t road_class[2]; // U16
+  uint8_t feature_code[4]; // U32
+  uint8_t exit_label_size;
+  char exit_label[1];
+  // comment_size U8
+  // comment[comment_size]
+  // shape_pt_count U32
+} msg_route_point_t;
+
+// Output Route Header (Name) Message
+// Message ID: 0xB052
+typedef struct {
+  uint8_t total[2]; // U16
+  uint8_t index[2]; // U16
+  char name[64];
+  uint8_t type;
+  uint8_t total_route_point[4]; // U32
+  uint8_t total_shape_point[4]; // U32
+} msg_route_header_t;
+
+// Input Upload Route Header Message
+// Message ID: 0xB055
+typedef struct {
+  char name[64];
+  uint8_t type;
+  uint8_t total_route_point[4]; // U32
+  uint8_t total_shape_point[4]; // U32
+} msg_route_header_in_t;
+
+// Output Navigation Message
+// Message ID: 0xA010
+typedef struct {
+  uint8_t gps_week[2]; // U16
+  uint8_t time_of_week[8]; // D64 sec
+  uint8_t year[2]; // U16
+  uint8_t month;
+  uint8_t day;
+  uint8_t hour;
+  uint8_t minute;
+  uint8_t second;
+  uint8_t satellites;
+  uint8_t latitude[8]; // D64 deg
+  uint8_t longitude[8]; // D64 deg
+  uint8_t elevation[8]; // D64 meters
+  uint8_t geoid_offset[2]; // S16 meters * 10
+  uint8_t speed[4]; // F32 km/h
+  uint8_t heading[2]; // U16 deg * 100
+  uint8_t magnetic_variation[2]; // S16 deg * 100
+  uint8_t fix_status;
+} msg_navigation_t;
+
+// Output Satellite Info Message
+// Message ID: 0xA020
+typedef struct {
+  uint8_t gps_week[2]; // U16
+  uint8_t time_of_week[8]; // D64 sec
+  uint8_t hdop[2]; // U16
+  uint8_t vdop[2]; // U16
+  uint8_t pdop[2]; // U16
+  uint8_t number;
+  struct {
+    uint8_t prn;
+    uint8_t azimuth[2]; // S16 deg? * 100
+    uint8_t elevation[2]; // S16 deg? * 100
+    uint8_t Cn0[2]; // U16 snr * 100
+    uint8_t status;
+  } sat[1];
+} msg_satellite_t;
+
+// Output Version Message
+// Message ID: 0xA001
+typedef struct {
+  uint8_t firmware_version[4];
+  char company[32];
+  char product[32];
+  char firmware[32];
+  char gps_firmware[48];
+  char serial[16];
+  char extra[16];
+} msg_version_t;
+
+// Output Device Capabilities Message
+// Message ID: 0xB001
+typedef struct {
+  uint8_t max_waypoints[4]; // U32
+  uint8_t max_tracks[2]; // U16
+  uint8_t max_track_points[4]; // U32
+  uint8_t max_routes[2]; // U16
+  uint8_t max_route_points[4]; // U32
+  uint8_t max_route_shape_points[4]; // U32
+  uint8_t max_maps[2]; // U16
+  uint8_t min_map_version[2]; // U16
+  uint8_t max_map_version[2]; // U16
+  uint8_t total_internal_file_memory[4]; // U32
+  uint8_t avail_internal_file_memory[4]; // U32
+  uint8_t total_external_file_memory[4]; // U32
+  uint8_t avail_external_file_memory[4]; // U32
+} msg_capabilities_t;
+
+//-----------------------------------------------------------------------------
+
+#if __APPLE__ || __linux
+#include <sys/time.h>
+#endif
+
+static void
+debug_out(const char* fmt, ...)
+{
+  va_list ap;
+  va_start(ap, fmt);
+  fputs(MYNAME ": ", stderr);
+  vfprintf(stderr, fmt, ap);
+  va_end(ap);
+}
+
+static void
+debug_out_time(const char* s)
+{
+#if __APPLE__ || __linux
+  struct timeval tv;
+  gettimeofday(&tv, NULL);
+  debug_out("%u.%03u %s", (unsigned)tv.tv_sec & 0xf, (unsigned)tv.tv_usec / 1000, s);
+#else
+  debug_out("%u %s", (unsigned)time(NULL) & 0xf, s);
+#endif
+}
+
+//-----------------------------------------------------------------------------
+
+static uint16_t
+checksum(const uint8_t* p, unsigned n)
+{
+  int x = 0;
+  unsigned i;
+  for (i = n / 2; i > 0; i--) {
+    x += *p++;
+    x += *p++ << 8;
+  }
+  if (n & 1) {
+    x += *p;
+  }
+  return (uint16_t)-x;
+}
+
+//-----------------------------------------------------------------------------
+// OS packet read/write wrappers
+
+static unsigned
+packet_read(void* buf)
+{
+  unsigned n = delbin_os_ops.packet_read(buf);
+  if (n == 0) {
+    fatal(MYNAME ": read 0\n");
+  }
+  if (global_opts.debug_level >= DBGLVL_H) {
+    unsigned j;
+    const uint8_t* p = (const uint8_t*) buf;
+
+    debug_out_time("pcktrd");
+    for (j = 0; j < n; j++) {
+      warning(" %02x", p[j]);
+    }
+    if (global_opts.debug_level >= DBGLVL_H2) {
+      warning("  ");
+      for (j = 0; j < n; j++) {
+        int c = p[j];
+        warning("%c", isprint(c) ? c : '.');
+      }
+    }
+    warning("\n");
+  }
+  return n;
+}
+
+static void
+packet_write(const void* buf, unsigned size)
+{
+  unsigned n;
+  if (global_opts.debug_level >= DBGLVL_H) {
+    unsigned j;
+    const uint8_t* p = (const uint8_t*) buf;
+
+    debug_out_time("pcktwr");
+    for (j = 0; j < size; j++) {
+      warning(" %02x", p[j]);
+    }
+    if (global_opts.debug_level >= DBGLVL_H2) {
+      warning("  ");
+      for (j = 0; j < size; j++) {
+        int c = p[j];
+        warning("%c", isprint(c) ? c : '.');
+      }
+    }
+    warning("\n");
+  }
+  n = delbin_os_ops.packet_write(buf, size);
+  if (n != size) {
+    fatal(MYNAME ": short write %u %u\n", size, n);
+  }
+}
+
+//-----------------------------------------------------------------------------
+
+// dynamically sized buffer with space reserved for message header and trailer
+typedef struct {
+  // message data size
+  unsigned size;
+  // buffer size
+  unsigned capacity;
+  uint8_t* buf;
+  // convenience pointer to message data area
+  void* data;
+} message_t;
+
+static void
+message_init(message_t* m)
+{
+  m->capacity = 100;
+  m->buf = (uint8_t*)xmalloc(m->capacity);
+  m->data = m->buf + 2 + 8;
+}
+
+static void
+message_init_size(message_t* m, unsigned size)
+{
+  m->size = size;
+  m->capacity = 2 + 8 + size + 4;
+  m->buf = (uint8_t*)xmalloc(m->capacity);
+  m->data = m->buf + 2 + 8;
+}
+
+static void
+message_free(message_t* m)
+{
+  xfree(m->buf);
+  m->buf = NULL;
+  m->data = NULL;
+}
+
+static void
+message_ensure_size(message_t* m, unsigned size)
+{
+  m->size = size;
+  if (m->capacity < 2 + 8 + size + 4) {
+    m->capacity = 2 + 8 + size + 4;
+    xfree(m->buf);
+    m->buf = (uint8_t*)xmalloc(m->capacity);
+    m->data = m->buf + 2 + 8;
+  }
+}
+
+static unsigned
+message_get_id(const message_t* m)
+{
+  return le_readu16(m->buf + 4);
+}
+
+//-----------------------------------------------------------------------------
+
+static void
+message_write(unsigned msg_id, message_t* m)
+{
+  unsigned chksum;
+  unsigned count;
+  unsigned n;
+  uint8_t* p = m->buf;
+
+  // header (2 start bytes filled in later)
+  p[2] = 0xdb;
+  p[3] = 0xfe;
+  le_write16(p + 4, msg_id);
+  // "data size" includes 4 trailer bytes
+  le_write16(p + 6, m->size + 4);
+  chksum = checksum(p + 2, 6);
+  le_write16(p + 8, chksum);
+  // message data (filled in by caller)
+  chksum = checksum((uint8_t*) m->data, m->size);
+  n = 2 + 8 + m->size;
+  // trailer (checksum and marker bytes)
+  le_write16(p + n, chksum);
+  p[n + 2] = 0xad;
+  p[n + 3] = 0xbc;
+  // size of message not counting packet start bytes
+  count = 8 + m->size + 4;
+  do {
+    const uint8_t save0 = p[0];
+    const uint8_t save1 = p[1];
+    n = delbin_os_packet_size - 2;
+    if (n > count) {
+      n = count;
+    }
+    // doc. says 0x20, device sends 0, probably ignored
+    p[0] = 0x20;
+    // valid bytes in packet after first 2
+    p[1] = n;
+    packet_write(p, 2 + n);
+    p[0] = save0;
+    p[1] = save1;
+    p += n;
+    count -= n;
+  } while (count != 0);
+  if (global_opts.debug_level >= DBGLVL_M) {
+    warning(MYNAME ": sent %x\n", msg_id);
+  }
+}
+
+// read from the payload of a single packet
+static unsigned
+read_depacketize_1(uint8_t** p, unsigned n, int new_packet)
+{
+  static uint8_t buf[256];
+  static unsigned buf_i, buf_n;
+  if (new_packet) {
+    buf_n = 0;
+  }
+  while (buf_n == 0) {
+    packet_read(buf);
+    if (buf[1] <= delbin_os_packet_size - 2) {
+      buf_n = buf[1];
+      buf_i = 2;
+    }
+  }
+  *p = buf + buf_i;
+  if (n > buf_n) {
+    n = buf_n;
+  }
+  buf_n -= n;
+  buf_i += n;
+  return n;
+}
+
+// read from packet payloads until request is fulfilled
+static void
+read_depacketize(uint8_t* buf, unsigned n)
+{
+  while (n) {
+    uint8_t* p;
+    unsigned nn = read_depacketize_1(&p, n, FALSE);
+    memcpy(buf, p, nn);
+    n -= nn;
+    buf += nn;
+  }
+}
+
+// Get one valid message.
+// If a corrupted message with the right id is seen, return failure (0).
+static unsigned
+message_read_1(unsigned msg_id, message_t* m)
+{
+  unsigned id;
+  for (;;) {
+    unsigned total;
+    unsigned n;
+    uint8_t buf[8];
+    uint8_t* p;
+
+    n = read_depacketize_1(&p, 8, FALSE);
+    memset(buf, 0, 8);
+    memcpy(buf, p, n);
+    while (buf[0] != 0xdb || buf[1] != 0xfe || checksum(buf, 6) != le_readu16(buf + 6)) {
+      // try for a message start at the beginning of next packet
+      n = read_depacketize_1(&p, 8, TRUE);
+      memset(buf, 0, 8);
+      memcpy(buf, p, n);
+    }
+    id = le_readu16(buf + 2);
+    total = le_readu16(buf + 4);
+    message_ensure_size(m, total - 4);
+    // copy in message head, really only need id field, do the rest for debugging
+    m->buf[0] = m->buf[1] = 0;
+    memcpy(m->buf + 2, buf, 8);
+    // read message body and trailer
+    read_depacketize((uint8_t*) m->data, total);
+    p = (uint8_t*)m->data + m->size;
+    if (checksum((uint8_t*) m->data, m->size) == le_readu16(p) &&
+        p[2] == 0xad && p[3] == 0xbc) {
+      if (global_opts.debug_level >= DBGLVL_M) {
+        warning(MYNAME ": received %x\n", id);
+      }
+      break;
+    }
+    if (global_opts.debug_level >= DBGLVL_L) {
+      warning(MYNAME ": corrupted message %x\n", id);
+    }
+    if (id == msg_id) {
+      id = 0;
+      break;
+    }
+  }
+  return id;
+}
+
+// Send MSG_ACK for given message
+static void
+message_ack(unsigned id, const message_t* m)
+{
+  message_t ack;
+  char* p1;
+  const char* p2 = (const char*) m->data;
+  switch (id) {
+  case MSG_ACK:
+  case MSG_NACK:
+  case MSG_NAVIGATION:
+  case MSG_SATELLITE_INFO:
+    // don't ack these
+    return;
+  }
+  message_init_size(&ack, 4);
+  p1 = (char*) ack.data;
+  // ack payload is id and body checksum of acked message
+  le_write16(p1, id);
+  p1[2] = p2[m->size];
+  p1[3] = p2[m->size + 1];
+  message_write(MSG_ACK, &ack);
+  message_free(&ack);
+}
+
+// Get specific message, ignoring others. Sends ACK for non-interval messages.
+// Gives up after at least READ_TIMEOUT-1 seconds have passed.
+static int
+message_read(unsigned msg_id, message_t* m)
+{
+  unsigned id;
+  time_t time_start = time(NULL);
+
+  if (global_opts.debug_level >= DBGLVL_M) {
+    warning(MYNAME ": looking for %x\n", msg_id);
+  }
+  for (;;) {
+    id = message_read_1(msg_id, m);
+    if (id == 0) {
+      break;
+    }
+    if (id == MSG_ERROR) {
+      const uint8_t* p = (const uint8_t*) m->data;
+      fatal(MYNAME ": device error %u: \"%s\"\n", *p, p + 1);
+    }
+    message_ack(id, m);
+    if (id == msg_id || time(NULL) - time_start >= READ_TIMEOUT) {
+      break;
+    }
+  }
+  return id == msg_id;
+}
+
+// Read a sequence of messages, up to a MSG_TRANSFER_COMPLETE
+static int
+get_batch(message_t** array, unsigned* n)
+{
+  int success = 1;
+  unsigned array_max = 100;
+  message_t* a = (message_t*) xmalloc(array_max * sizeof(message_t));
+  unsigned i = 0;
+  unsigned id;
+  if (global_opts.debug_level >= DBGLVL_M) {
+    warning(MYNAME ": begin get_batch\n");
+  }
+  do {
+    time_t time_start = time(NULL);
+    if (i == array_max) {
+      message_t* old_a = a;
+      array_max += array_max;
+      a = (message_t*) xmalloc(array_max * sizeof(message_t));
+      memcpy(a, old_a, i * sizeof(message_t));
+      xfree(old_a);
+    }
+    message_init(&a[i]);
+    for (;;) {
+      id = message_read_1(0, &a[i]);
+      switch (id) {
+      case MSG_NAVIGATION:
+        if (time(NULL) - time_start >= READ_TIMEOUT) {
+          success = 0;
+          break;
+        }
+        // fall through
+      case MSG_ACK:
+      case MSG_NACK:
+      case MSG_SATELLITE_INFO:
+        continue;
+      }
+      break;
+    }
+    message_ack(id, &a[i]);
+    i++;
+  } while (success && id != MSG_TRANSFER_COMPLETE);
+  if (success) {
+    *array = a;
+    *n = i - 1;
+    message_free(&a[*n]);
+    if (global_opts.debug_level >= DBGLVL_M) {
+      warning(MYNAME ": end get_batch, %u messages\n", *n);
+    }
+  } else {
+    while (i--) {
+      message_free(&a[i]);
+    }
+    xfree(a);
+    *array = NULL;
+    *n = 0;
+    if (global_opts.debug_level >= DBGLVL_M) {
+      warning(MYNAME ": end get_batch, failed\n");
+    }
+  }
+  return success;
+}
+
+typedef struct {
+  unsigned msg_id;
+  message_t msg;
+} batch_array_t;
+
+static batch_array_t* batch_array;
+
+static unsigned batch_array_max;
+static unsigned batch_array_i;
+
+// add a message to sequence that will later be sent all at once
+static void
+add_to_batch(unsigned id, message_t* m)
+{
+  if (batch_array_i == batch_array_max) {
+    char* old = (char*) batch_array;
+    if (batch_array_max == 0) {
+      batch_array_max = 50;
+    }
+    batch_array_max += batch_array_max;
+    batch_array = (batch_array_t*) xmalloc(batch_array_max * sizeof(*batch_array));
+    if (batch_array_i) {
+      memcpy(batch_array, old, batch_array_i * sizeof(*batch_array));
+      xfree(old);
+    }
+  }
+  batch_array[batch_array_i].msg_id = id;
+  batch_array[batch_array_i].msg = *m;
+  batch_array_i++;
+  memset(m, 0, sizeof(*m));
+}
+
+// send an accumulated sequence of messages
+static void
+send_batch(void)
+{
+  message_t m;
+  const unsigned n = batch_array_i;
+  unsigned i;
+  unsigned progress = 0;
+
+  message_init(&m);
+  if (global_opts.debug_level >= DBGLVL_M) {
+    warning(MYNAME ": begin send_batch, %u messages\n", n);
+  }
+  for (i = 0; i < n; i++) {
+    unsigned timeout_count = 0;
+    time_t time_start = time(NULL);
+
+    // Can't really trigger this off either i or n as we don't
+    // know how the various packets map to actual waypts.
+    if (global_opts.verbose_status &&
+        (batch_array[i].msg_id == MSG_WAYPOINT_IN)) {
+      waypt_status_disp(waypoint_n, ++progress);
+    }
+
+    message_write(batch_array[i].msg_id, &batch_array[i].msg);
+    for (;;) {
+      unsigned id = message_read_1(0, &m);
+      switch (id) {
+      case MSG_ACK:
+        break;
+      case MSG_NAVIGATION:
+        if (time(NULL) - time_start >= 2) {
+          if (timeout_count) {
+            fatal(MYNAME ": send_batch timed out\n");
+          }
+          timeout_count++;
+          if (global_opts.debug_level >= DBGLVL_M) {
+            warning(MYNAME ": re-sending %x\n", batch_array[i].msg_id);
+          }
+          message_write(batch_array[i].msg_id, &batch_array[i].msg);
+          time_start = time(NULL);
+        }
+        // fall through
+      case MSG_NACK:
+      case MSG_SATELLITE_INFO:
+        continue;
+      default:
+        warning(MYNAME ": unexpected response message %x during send_batch\n", id);
+        continue;
+      }
+      break;
+    }
+  }
+  message_read(MSG_TRANSFER_COMPLETE, &m);
+  if (global_opts.debug_level >= DBGLVL_M) {
+    warning(MYNAME ": end send_batch\n");
+  }
+  for (i = n; i--;) {
+    message_free(&batch_array[i].msg);
+  }
+  xfree(batch_array);
+  message_free(&m);
+  batch_array_i = batch_array_max = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Coordinate conversion
+
+static double
+delbin_rad2deg(int32_t x)
+{
+  return x * ((180 / M_PI) / 100000000);
+}
+
+static int32_t
+delbin_deg2rad(double x)
+{
+  return (int32_t)(x * ((M_PI / 180) * 100000000));
+}
+
+//-----------------------------------------------------------------------------
+// Waypoint reading
+
+static time_t
+decode_time(const uint8_t* p)
+{
+  struct tm t;
+  t.tm_year = p[0];
+  t.tm_mon  = p[1] - 1;
+  t.tm_mday = p[2];
+  t.tm_hour = p[3];
+  t.tm_min  = p[4];
+  t.tm_sec  = p[5];
+  return mkgmtime(&t);
+}
+
+static Waypoint*
+decode_waypoint(const void* data)
+{
+  Waypoint* wp = new Waypoint;
+  const msg_waypoint_t* p = (const msg_waypoint_t*)data;
+  const char* s;
+  float f;
+
+  wp->SetCreationTime(decode_time(&p->year));
+  wp->latitude = delbin_rad2deg(le_read32(p->latitude));
+  wp->longitude = delbin_rad2deg(le_read32(p->longitude));
+  f = le_read_float(p->elevation);
+  if (f > UNKNOWN_ELEV) {
+    wp->altitude = f;
+  }
+  wp->icon_descr = waypoint_symbol(p->symbol);
+//  if (!wp->icon_descr.isNull()) {
+//    wp->icon_descr = wp->icon_descr;
+//  }
+  if (p->name_size && p->name[0]) {
+    wp->description = p->name;
+  }
+  s = p->name + p->name_size;
+  if (le_readu16(s) &&  s[2]) {
+    wp->notes = xstrdup(s + 2);
+  }
+  return wp;
+}
+
+static void
+read_waypoints(void)
+{
+  message_t m;
+  message_t* msg_array;
+  unsigned msg_array_n;
+  Waypoint* wp = NULL;
+  unsigned n_point;
+  unsigned notes_i = 0;
+  unsigned notes_max = 0;
+  unsigned i;
+  int attempt = ATTEMPT_MAX;
+
+  message_init(&m);
+  // get number of waypoints
+  for (;;) {
+    m.size = 0;
+    message_write(MSG_WAYPOINT_COUNT, &m);
+    if (message_read(MSG_WAYPOINT_COUNT, &m)) {
+      break;
+    }
+    if (--attempt == 0) {
+      fatal(MYNAME ": reading waypoint count failed\n");
+    }
+  }
+  n_point = le_readu32(m.data);
+  if (global_opts.debug_level >= DBGLVL_L) {
+    warning(MYNAME ": %u waypoints\n", n_point);
+  }
+  if (n_point == 0) {
+    message_free(&m);
+    return;
+  }
+  // get waypoint messages
+  attempt = ATTEMPT_MAX;
+  for (;;) {
+    m.size = MSG_REQUEST_WAYPOINTS_SIZE;
+    memset(m.data, 0, m.size);
+    // This byte is documented as reserved. Setting it to 3 is required to get
+    // extended notes (message 0xb015) with PN-40 firmware 2.5.
+    // Whether it has any effect with earlier firmware or the PN-20 is unknown.
+    ((char*)m.data)[1] = 3;
+    message_write(MSG_REQUEST_WAYPOINTS, &m);
+    if (get_batch(&msg_array, &msg_array_n)) {
+      break;
+    }
+    if (--attempt == 0) {
+      fatal(MYNAME ": reading waypoints failed\n");
+    }
+    if (global_opts.debug_level >= DBGLVL_M) {
+      warning(MYNAME ": timed out reading waypoints, retrying\n");
+    }
+    m.size = MSG_BREAK_SIZE;
+    memset(m.data, 0, m.size);
+    message_write(MSG_BREAK, &m);
+  }
+  message_free(&m);
+  // process waypoint messages
+  for (i = 0; i < msg_array_n; i++) {
+    unsigned id = message_get_id(&msg_array[i]);
+    if (id == MSG_WAYPOINT_OUT) {
+      wp = decode_waypoint(msg_array[i].data);
+      waypt_add(wp);
+      notes_i = 0;
+      notes_max = 0;
+      if (global_opts.debug_level >= DBGLVL_L) {
+        warning(MYNAME ": read waypoint '%s'\n", qPrintable(wp->description));
+      }
+    } else if (wp && id == MSG_WAYPOINT_NOTE_OUT) {
+      const msg_waypoint_note_t* p = (const msg_waypoint_note_t*) msg_array[i].data;
+      const char* s = p->name + p->name_size;
+      unsigned nn = le_readu16(s);
+      if (notes_max < notes_i + nn) {
+#if NEW_STRINGS
+// This section needs a serious rethinking.
+#else
+        char* old = wp->notes;
+#endif
+        if (notes_max == 0) {
+          notes_max = nn;
+        }
+        do {
+          notes_max += notes_max;
+        } while (notes_max < notes_i + nn);
+        wp->notes = (char*) xmalloc(notes_max);
+#if NEW_STRINGS
+#else
+        if (old) {
+          memcpy(wp->notes, old, notes_i);
+          xfree(old);
+        }
+#endif
+      }
+      if (nn) {
+#if NEW_STRINGS
+        // Is this really what this code was trying to do?
+        wp->notes += QString::fromUtf8(s + 2, nn);
+#else
+        memcpy(wp->notes + notes_i, s + 2, nn);
+#endif
+        notes_i += nn;
+        if (wp->notes[notes_i - 1] == 0) {
+          notes_i--;
+        }
+      }
+    } else {
+      fatal(MYNAME ": unexpected message %x while reading waypoints\n", id);
+    }
+    message_free(&msg_array[i]);
+  }
+  xfree(msg_array);
+}
+
+//-----------------------------------------------------------------------------
+// Waypoint writing
+
+static void
+encode_time(time_t time_, uint8_t* p)
+{
+  const struct tm* t = gmtime(&time_);
+  p[0] = t->tm_year;
+  p[1] = t->tm_mon + 1;
+  p[2] = t->tm_mday;
+  p[3] = t->tm_hour;
+  p[4] = t->tm_min;
+  p[5] = t->tm_sec;
+}
+
+static void
+get_gc_notes(const Waypoint* wp, int* symbol, char** notes, unsigned* notes_size)
+{
+  fs_xml* fs_gpx;
+  xml_tag* root = NULL;
+  gbfile* fd = gbfopen(NULL, "w", MYNAME);
+  const char* size = NULL;
+  int gc_sym = 0;
+
+  switch (wp->gc_data->type) {
+  case gt_traditional:
+    gc_sym = 160;
+    break;
+  case gt_multi:
+    gc_sym = 161;
+    break;
+  case gt_virtual:
+    gc_sym = 169;
+    break;
+  case gt_letterbox:
+    gc_sym = 163;
+    break;
+  case gt_event:
+    gc_sym = 165;
+    break;
+  case gt_suprise:
+    gc_sym = 162;
+    break;
+  case gt_webcam:
+    gc_sym = 170;
+    break;
+  case gt_earth:
+    gc_sym = 168;
+    break;
+  case gt_benchmark:
+    gc_sym = 172;
+    break;
+  case gt_cito:
+    gc_sym = 167;
+    break;
+  case gt_mega:
+    gc_sym = 166;
+    break;
+  case gt_wherigo:
+    gc_sym = 164;
+    break;
+  case gt_unknown:
+  case gt_locationless:
+  case gt_ape:
+    break;
+  }
+  if (0 == (wp->icon_descr.compare("Geocache Found"))) {
+    gc_sym = 124;
+  }
+  if (!wp->description.isEmpty()) {
+    gbfputs(wp->description, fd);
+    if (!wp->gc_data->placer.isEmpty()) {
+      gbfprintf(fd, " by %s", CSTR(wp->gc_data->placer));
+    }
+    gbfputc('\n', fd);
+  }
+
+  gbfprintf(fd, "Cache ID: %s\n", CSTRc(wp->shortname));
+  if (gc_sym && opt_gcsym && atoi(opt_gcsym)) {
+    gbfprintf(fd, "%s\n", waypoint_symbol(gc_sym));
+    *symbol = gc_sym;
+  } else if (!wp->icon_descr.isNull()) {
+    gbfprintf(fd, "%s\n", CSTR(wp->icon_descr));
+  }
+  switch (wp->gc_data->container) {
+  case gc_micro:
+    size = "Micro";
+    break;
+  case gc_small:
+    size = "Small";
+    break;
+  case gc_regular:
+    size = "Regular";
+    break;
+  case gc_large:
+    size = "Large";
+    break;
+  case gc_unknown:
+    size = "Not Chosen" ;
+    break;
+  case gc_other:
+    size = "Other";
+    break;
+    // Device has no symbol for this, but this is what Topo sends.
+  case gc_virtual:
+    size = "Virtual";
+    break;
+  default:
+    break;
+  }
+  if (size) {
+    gbfprintf(fd, "SIZE: %s\n", size);
+  }
+  if (wp->gc_data->diff % 10) {
+    gbfprintf(fd, "D%.1f", wp->gc_data->diff / 10.0);
+  } else {
+    gbfprintf(fd, "D%u", wp->gc_data->diff / 10);
+  }
+  if (wp->gc_data->terr % 10) {
+    gbfprintf(fd, "/T%.1f\n", wp->gc_data->terr / 10.0);
+  } else {
+    gbfprintf(fd, "/T%u\n", wp->gc_data->terr / 10);
+  }
+  if (!wp->gc_data->hint.isEmpty() && !opt_hint_at_end) {
+    gbfprintf(fd, "HINT: %s\n", CSTR(wp->gc_data->hint));
+  }
+  if (!wp->gc_data->desc_short.utfstring.isEmpty() || !wp->gc_data->desc_long.utfstring.isEmpty()) {
+    gbfputs("DESC: ", fd);
+    if (!wp->gc_data->desc_short.utfstring.isEmpty()) {
+      char* s1 = strip_html(&wp->gc_data->desc_short);
+      char* s2 = cet_str_utf8_to_any(s1, global_opts.charset);
+      gbfprintf(fd, "%s\n", s2);
+      xfree(s2);
+      xfree(s1);
+    }
+    if (!wp->gc_data->desc_long.utfstring.isEmpty()) {
+      char* s1 = strip_html(&wp->gc_data->desc_long);
+      char* s2 = cet_str_utf8_to_any(s1, global_opts.charset);
+      gbfputs(s2, fd);
+      xfree(s2);
+      xfree(s1);
+    }
+  }
+  fs_gpx = (fs_xml*)fs_chain_find(wp->fs, FS_GPX);
+  if (opt_logs && fs_gpx && fs_gpx->tag) {
+    root = xml_findfirst(fs_gpx->tag, "groundspeak:logs");
+  }
+  if (root) {
+    xml_tag* curlog = xml_findfirst(root, "groundspeak:log");
+    if (curlog) {
+      gbfputs("\nLOG:\n", fd);
+    }
+    for (; curlog; curlog = xml_findnext(root, curlog, "groundspeak:log")) {
+      xml_tag* logpart = xml_findfirst(curlog, "groundspeak:type");
+      if (logpart) {
+        gbfprintf(fd, "%s\n", CSTR(logpart->cdata));
+      }
+      logpart = xml_findfirst(curlog, "groundspeak:date");
+      if (logpart) {
+        time_t logtime = xml_parse_time(logpart->cdata).toTime_t();
+        const struct tm* logtm = gmtime(&logtime);
+        gbfprintf(fd, "%d-%02d-%02d ", logtm->tm_year + 1900, logtm->tm_mon + 1, logtm->tm_mday);
+      }
+      logpart = xml_findfirst(curlog, "groundspeak:finder");
+      if (logpart) {
+        char* s = cet_str_utf8_to_any(CSTR(logpart->cdata), global_opts.charset);
+        gbfputs(s, fd);
+        xfree(s);
+      }
+      logpart = xml_findfirst(curlog, "groundspeak:text");
+      if (logpart) {
+        char* s = cet_str_utf8_to_any(CSTR(logpart->cdata), global_opts.charset);
+        gbfprintf(fd, ", %s", s);
+        xfree(s);
+      }
+      gbfputc('\n', fd);
+    }
+  }
+  if (!wp->gc_data->hint.isEmpty() && opt_hint_at_end) {
+    gbfprintf(fd, "\nHINT: %s\n", CSTR(wp->gc_data->hint));
+  }
+  gbfputc(0, fd);
+  *notes_size = fd->memlen;
+  *notes = (char*) xmalloc(*notes_size);
+  memcpy(*notes, fd->handle.mem, *notes_size);
+  gbfclose(fd);
+}
+
+static void
+write_waypoint_notes(const char* notes, unsigned size, const char* name)
+{
+  message_t m;
+  const unsigned name_size = strlen(name) + 1;
+  const unsigned bytes_per_msg = (10 * (delbin_os_packet_size - 2)) - name_size - 20;
+  const unsigned msg_count = (size + (bytes_per_msg - 1)) / bytes_per_msg;
+  unsigned i = 1;
+
+  do {
+    char* pp;
+    unsigned n = bytes_per_msg;
+    msg_waypoint_note_t* p;
+    message_init_size(&m, 2 + 2 + 1 + name_size + 2 + bytes_per_msg);
+    p = (msg_waypoint_note_t*) m.data;
+    le_write16(p->index, i++);
+    le_write16(p->total, msg_count);
+    p->name_size = name_size;
+    memcpy(p->name, name, p->name_size);
+    pp = p->name + p->name_size;
+    if (n > size) {
+      n = size;
+    }
+    le_write16(pp, n);
+    pp += 2;
+    memcpy(pp, notes, n);
+    pp += n;
+    if (*(pp - 1)) {
+      *pp++ = 0;
+    }
+    notes += n;
+    size -= n;
+    m.size = pp - (char*)p;
+    add_to_batch(MSG_WAYPOINT_NOTE_IN, &m);
+  } while (size != 0);
+}
+
+static void
+add_nuke(nuke_type type)
+{
+  message_t m;
+  msg_delete_t* p;
+
+  message_init_size(&m, MSG_DELETE_SIZE);
+  p = (msg_delete_t*) m.data;
+  p->type = type;
+  p->mode = nuke_mode_all;
+  p->location = nuke_dest_internal;
+  memset(p->object_name, 0, sizeof(p->object_name));
+
+  // MSG_DELETE generates a MSG_TRANSFER_COMPLETE,
+  // so use the batch facility to wait for it
+  add_to_batch(MSG_DELETE, &m);
+  send_batch();
+}
+
+static void
+write_waypoint(const Waypoint* wp)
+{
+  message_t m;
+  msg_waypoint_t* p;
+  QString name = wp->shortname;
+  char* notes;
+  unsigned name_size;
+  unsigned notes_size = 0;
+  unsigned extended_notes_size = 0;
+  const char* notes_freeable = NULL;
+  int symbol = -1;
+  float elev = UNKNOWN_ELEV;
+  char* pp;
+
+  if (wp->EmptyGCData()) {
+    notes = xstrdup(wp->notes);
+    if (notes == NULL && wp->description.isEmpty() && wp->shortname != wp->description) {
+      notes = xstrdup(wp->description);
+    }
+    if (notes) {
+      notes_size = strlen(notes) + 1;
+    }
+  } else {
+    get_gc_notes(wp, &symbol, &notes, &notes_size);
+    notes_freeable = notes;
+    if (!wp->description.isEmpty()) {
+      name = mkshort(mkshort_handle, wp->description);
+    }
+  }
+
+  if (notes_size > 800) {
+    if (use_extended_notes) {
+      extended_notes_size = notes_size;
+      notes_size = 1;
+    } else {
+      notes_size = 800;
+    }
+  }
+
+  name_size = strlen(CSTRc(name)) + 1;
+  if (name_size > 255) {
+    name_size = 255;
+  }
+  message_init_size(&m, 31 + name_size + notes_size);
+  p = (msg_waypoint_t*) m.data;
+
+  waypoint_i++;
+  le_write32(p->total, waypoint_n);
+  le_write32(p->index, waypoint_i);
+  encode_time(wp->GetCreationTime().toTime_t(), &p->year);
+  le_write32(p->latitude, delbin_deg2rad(wp->latitude));
+  le_write32(p->longitude, delbin_deg2rad(wp->longitude));
+  if (wp->altitude > unknown_alt) {
+    elev = wp->altitude;
+  }
+  le_write_float(p->elevation, elev);
+  if (symbol < 0) {
+    symbol = 0;
+    if (!wp->icon_descr.isNull()) {
+      symbol = waypoint_symbol_index(CSTR(wp->icon_descr));
+    }
+  }
+  p->symbol = symbol;
+  p->name_size = name_size;
+  memcpy(p->name, CSTRc(name), name_size - 1);
+  p->name[name_size - 1] = 0;
+  pp = p->name + name_size;
+  m.size = (pp + 2 + notes_size) - (char*)p;
+  if (extended_notes_size) {
+    le_write16(pp, 0xffff);
+    pp[2] = 0;
+  } else {
+    le_write16(pp, notes_size);
+#if NEW_STRINGS
+#else
+    if (notes) {
+      memcpy(pp + 2, notes, notes_size - 1);
+      pp[2 + notes_size - 1] = 0;
+    }
+#endif
+  }
+
+  add_to_batch(MSG_WAYPOINT_IN, &m);
+
+  if (extended_notes_size) {
+    write_waypoint_notes(notes, extended_notes_size, CSTRc(name));
+  }
+  if (notes_freeable) {
+    xfree(notes_freeable);
+  }
+  if (global_opts.debug_level >= DBGLVL_L) {
+    warning(MYNAME ": wrote waypoint %u '%s'\n", waypoint_i, qPrintable(name));
+  }
+}
+
+static void
+write_waypoints(void)
+{
+  message_t m;
+  unsigned device_n = 0;
+
+  waypoint_i = 0;
+  waypoint_n = waypt_count();
+  if (waypoint_n > device_max_waypoint) {
+    fatal(MYNAME ": waypoint count (%u) exceeds device limit (%u)\n",
+          waypoint_n, device_max_waypoint);
+  }
+
+  message_init_size(&m, 0);
+  message_write(MSG_WAYPOINT_COUNT, &m);
+  if (message_read(MSG_WAYPOINT_COUNT, &m)) {
+    device_n = le_readu32(m.data);
+  }
+
+  waypt_disp_all(write_waypoint);
+  send_batch();
+
+  if (device_n + waypoint_n > device_max_waypoint) {
+    m.size = 0;
+    message_write(MSG_WAYPOINT_COUNT, &m);
+    if (message_read(MSG_WAYPOINT_COUNT, &m) &&
+        le_readu32(m.data) == device_max_waypoint) {
+      warning(MYNAME ": waypoint count (%u already on device + %u added = %u)"
+              " exceeds device limit (%u), some may have been discarded\n",
+              device_n, waypoint_n, device_n + waypoint_n, device_max_waypoint);
+    }
+  }
+  message_free(&m);
+}
+
+//-----------------------------------------------------------------------------
+// Track reading
+
+static void
+decode_sat_fix(Waypoint* wp, const uint8_t status)
+{
+  switch (status & 3) {
+  case 1:
+    wp->fix = fix_none;
+    break;
+  case 2:
+    wp->fix = fix_2d;
+    break;
+  case 3:
+    wp->fix = fix_3d;
+    if (status & 4) {
+      wp->fix = fix_dgps;
+    }
+    break;
+  }
+}
+
+static void
+decode_track_point(const void* data, unsigned* wp_array_i, unsigned max_point)
+{
+  const msg_track_point_t* p = (const msg_track_point_t*) data;
+  const unsigned n = p->number;
+  unsigned i;
+  unsigned j = *wp_array_i;
+
+  if (j + n > max_point) {
+    fatal(MYNAME ": read too many track points\n");
+  }
+  for (i = 0; i < n; i++, j++) {
+    Waypoint* wp = new Waypoint;
+    float elev = le_read_float(p->point[i].elevation);
+    wp_array[j] = wp;
+    wp->SetCreationTime(decode_time(&p->point[i].year));
+    wp->latitude = delbin_rad2deg(le_read32(p->point[i].latitude));
+    wp->longitude = delbin_rad2deg(le_read32(p->point[i].longitude));
+    if (elev > UNKNOWN_ELEV) {
+      wp->altitude = elev;
+    }
+    wp->speed = le_readu16(p->point[i].speed);
+    wp->speed *= (100.0f / (60 * 60));
+    wp->wpt_flags.speed = 1;
+    decode_sat_fix(wp, p->point[i].status);
+    wp->wpt_flags.new_trkseg = (p->point[i].status & 0x10) != 0;
+  }
+  *wp_array_i = j;
+}
+
+static void
+read_track(route_head* track)
+{
+  message_t m;
+  message_t* msg_array;
+  const msg_track_header_t* p;
+  unsigned msg_array_n;
+  unsigned wp_array_i = 0;
+  unsigned n_point;
+  unsigned i;
+  int attempt = ATTEMPT_MAX;
+
+  message_init(&m);
+  // read track messages
+  for (;;) {
+    m.size = MSG_REQUEST_TRACKS_SIZE;
+    memset(m.data, 0, m.size);
+    ((char*)m.data)[0] = 1;  // Download single track
+    strcpy((char*)m.data + 1, CSTRc(track->rte_name));
+    message_write(MSG_REQUEST_TRACKS, &m);
+    if (get_batch(&msg_array, &msg_array_n)) {
+      break;
+    }
+    if (--attempt == 0) {
+      fatal(MYNAME ": reading track '%s' failed\n", qPrintable(track->rte_name));
+    }
+    if (global_opts.debug_level >= DBGLVL_M) {
+      warning(MYNAME ": timed out reading track '%s', retrying\n", qPrintable(track->rte_name));
+    }
+    m.size = MSG_BREAK_SIZE;
+    memset(m.data, 0, m.size);
+    message_write(MSG_BREAK, &m);
+  }
+  message_free(&m);
+  if (msg_array_n == 0 || message_get_id(&msg_array[0]) != MSG_TRACK_HEADER_OUT) {
+    fatal(MYNAME ": reading track '%s' failed (missing track header)\n", qPrintable(track->rte_name));
+  }
+  // process track messages
+  p = (const msg_track_header_t*) msg_array[0].data;
+  if (le_readu16(p->comment_size)) {
+    track->rte_desc = p->comment;
+  }
+  track->line_color.bbggrr = track_color(p->color[0]);
+  n_point = le_readu32(p->total_points);
+  wp_array = (Waypoint**) xcalloc(n_point, sizeof(*wp_array));
+  message_free(&msg_array[0]);
+  for (i = 1; i < msg_array_n; i++) {
+    unsigned id = message_get_id(&msg_array[i]);
+    if (id == MSG_TRACK_POINT_OUT) {
+      decode_track_point(msg_array[i].data, &wp_array_i, n_point);
+    } else {
+      fatal(MYNAME ": unexpected message %x while reading track '%s'\n", id, qPrintable(track->rte_name));
+    }
+    message_free(&msg_array[i]);
+  }
+  xfree(msg_array);
+  if (n_point != wp_array_i) {
+    fatal(MYNAME ": track point count mismatch, expected %u, got %u\n", n_point, wp_array_i);
+  }
+  if (global_opts.debug_level >= DBGLVL_L) {
+    warning(MYNAME ": read track '%s' %u points\n", qPrintable(track->rte_name), n_point);
+  }
+  for (i = 0; i < n_point; i++) {
+    track_add_wpt(track, wp_array[i]);
+  }
+  track_add_head(track);
+  xfree(wp_array);
+}
+
+static void
+read_tracks(void)
+{
+  message_t m;
+  message_t* msg_array;
+  unsigned msg_array_n;
+  route_head** track_array;
+  unsigned total;
+  unsigned i;
+  int attempt = ATTEMPT_MAX;
+
+  message_init(&m);
+  // get number of tracks
+  for (;;) {
+    m.size = 0;
+    message_write(MSG_TRACK_COUNT, &m);
+    if (message_read(MSG_TRACK_COUNT, &m)) {
+      break;
+    }
+    if (--attempt == 0) {
+      fatal(MYNAME ": reading track count failed\n");
+    }
+  }
+  total = le_readu32(m.data);
+  if (global_opts.debug_level >= DBGLVL_L) {
+    warning(MYNAME ": %u tracks\n", total);
+  }
+  if (total == 0) {
+    message_free(&m);
+    return;
+  }
+
+  // First get track headers, then request each track with non-zero number of points
+  attempt = ATTEMPT_MAX;
+  for (;;) {
+    m.size = MSG_REQUEST_TRACKS_SIZE;
+    memset(m.data, 0, m.size);
+    ((char*)m.data)[0] = 2;  // Download all track headers
+    message_write(MSG_REQUEST_TRACKS, &m);
+    if (get_batch(&msg_array, &msg_array_n)) {
+      break;
+    }
+    if (--attempt == 0) {
+      fatal(MYNAME ": reading track headers failed\n");
+    }
+    if (global_opts.debug_level >= DBGLVL_M) {
+      warning(MYNAME ": timed out reading track headers, retrying\n");
+    }
+    m.size = MSG_BREAK_SIZE;
+    memset(m.data, 0, m.size);
+    message_write(MSG_BREAK, &m);
+  }
+  message_free(&m);
+  track_array = (route_head**) xcalloc(total, sizeof(*track_array));
+  for (i = 0; i < msg_array_n; i++) {
+    unsigned id = message_get_id(&msg_array[i]);
+    if (id == MSG_TRACK_HEADER_OUT) {
+      const msg_track_header_t* p = (msg_track_header_t*) msg_array[i].data;
+      if (le_readu32(p->total_points)) {
+        track_array[i] = route_head_alloc();
+        track_array[i]->rte_name = p->name;
+      }
+    } else {
+      fatal(MYNAME ": unexpected message %x while reading track headers\n", id);
+    }
+    message_free(&msg_array[i]);
+  }
+  xfree(msg_array);
+  // get each track
+  for (i = 0; i < total; i++) {
+    if (track_array[i]) {
+      read_track(track_array[i]);
+    }
+  }
+  xfree(track_array);
+}
+
+//-----------------------------------------------------------------------------
+// Track writing
+
+static void
+write_track_points(void)
+{
+  message_t m;
+  const unsigned pt_per_msg = 10;
+  msg_track_point_t* p = NULL;
+  unsigned i = 0;
+  unsigned j = 0;
+
+  do {
+    const Waypoint* wp = wp_array[i];
+    float f;
+
+    if (j == 0) {
+      message_init_size(&m, 9 + 23 * pt_per_msg);
+      p =(msg_track_point_t*) m.data;
+      le_write32(p->total, waypoint_n);
+      le_write32(p->index, i + 1);
+    }
+    assert(p);
+    encode_time(wp->GetCreationTime().toTime_t(), &p->point[j].year);
+    le_write32(p->point[j].latitude, delbin_deg2rad(wp->latitude));
+    le_write32(p->point[j].longitude, delbin_deg2rad(wp->longitude));
+    f = UNKNOWN_ELEV;
+    if (wp->altitude > unknown_alt) {
+      f = wp->altitude;
+    }
+    le_write_float(p->point[j].elevation, f);
+    f = WAYPT_GET(wp, speed, 0);
+    f *= (60 * 60) / 100;
+    le_write16(p->point[j].speed, (uint16_t)f);
+    f = WAYPT_GET(wp, course, 0);
+    f *= 100;
+    le_write16(p->point[j].heading, (uint16_t)f);
+    switch (wp->fix) {
+    default:
+      p->point[j].status = 0;
+      break;
+    case fix_none:
+      p->point[j].status = 1;
+      break;
+    case fix_2d:
+      p->point[j].status = 2;
+      break;
+    case fix_3d:
+      p->point[j].status = 3;
+      break;
+    case fix_dgps:
+      p->point[j].status = 4 | 3;
+      break;
+    }
+    if (wp->wpt_flags.new_trkseg) {
+      p->point[j].status |= 0x10;
+    }
+    i++;
+    j++;
+    if (j == pt_per_msg || i == waypoint_n) {
+      p->number = j;
+      m.size = 9 + 23 * j;
+      add_to_batch(MSG_TRACK_POINT_IN, &m);
+      j = 0;
+    }
+  } while (i < waypoint_n);
+}
+
+static void
+write_track_begin(const route_head* track)
+{
+  waypoint_i = 0;
+  waypoint_n = track->rte_waypt_ct;
+  if (waypoint_n) {
+    wp_array = (Waypoint**) xmalloc(waypoint_n * sizeof(*wp_array));
+  }
+}
+
+static void
+write_track_point(const Waypoint* wp)
+{
+  wp_array[waypoint_i++] = (Waypoint*)wp;
+}
+
+static void
+write_track_end(const route_head* track)
+{
+  message_t m;
+  msg_track_header_in_t* p;
+  unsigned comment_size = 0;
+
+  if (waypoint_n == 0) {
+    return;
+  }
+  if (!track->rte_desc.isEmpty()) {
+    comment_size = strlen(CSTRc(track->rte_desc)) + 1;
+  }
+  message_init_size(&m, sizeof(msg_track_header_in_t) - 1 + comment_size);
+  p = (msg_track_header_in_t*) m.data;
+  memset(p->name, 0, sizeof(p->name));
+  if (!track->rte_name.isEmpty()) {
+    strncpy(p->name, CSTRc(track->rte_name), sizeof(p->name) - 1);
+  } else {
+    sprintf(p->name, "%lu", (long)wp_array[0]->GetCreationTime().toTime_t());
+  }
+  le_write32(p->total_points, waypoint_n);
+  encode_time(current_time().toTime_t(), &p->year);
+  le_write16(p->color, track_color_index(track->line_color.bbggrr));
+  le_write16(p->comment_size, comment_size);
+  if (comment_size) {
+    memcpy(p->comment, CSTRc(track->rte_desc), comment_size);
+  }
+  add_to_batch(MSG_TRACK_HEADER_IN, &m);
+  write_track_points();
+  send_batch();
+  xfree(wp_array);
+}
+
+static void
+write_tracks(void)
+{
+  track_disp_all(write_track_begin, write_track_end, write_track_point);
+}
+
+//-----------------------------------------------------------------------------
+// Route reading
+
+static void
+decode_route_shape(const void* data, unsigned* wp_array_i)
+{
+  const msg_route_shape_t* p = (msg_route_shape_t*) data;
+  const unsigned n = p->number;
+  unsigned i;
+  unsigned j = *wp_array_i;
+
+  for (i = 0; i < n; i++, j++) {
+    char buf[32];
+    Waypoint* wp = new Waypoint;
+    wp_array[j] = wp;
+    wp->latitude = delbin_rad2deg(le_read32(p->point[i].latitude));
+    wp->longitude = delbin_rad2deg(le_read32(p->point[i].longitude));
+    sprintf(buf, "SHP%03u", j);
+    wp->shortname = buf;
+  }
+  *wp_array_i = j;
+}
+
+static Waypoint*
+decode_route_point(const void* data)
+{
+  const msg_route_point_t* p = (const msg_route_point_t*) data;
+  const char* s = NULL;
+  gbfile* fd = gbfopen(NULL, "w", MYNAME);
+  Waypoint* wp = new Waypoint;
+  if (p->name[0]) {
+    wp->shortname = p->name;
+  }
+  // give these a higher priority than the shape points
+  wp->route_priority = 1;
+  wp->latitude = delbin_rad2deg(le_read32(p->latitude));
+  wp->longitude = delbin_rad2deg(le_read32(p->longitude));
+  switch (p->itinerary_type) {
+  case 1:
+    s = "Start";
+    break;
+  case 2:
+    s = "Stop";
+    break;
+  case 3:
+    s = "Finish";
+    break;
+  case 4:
+    s = "Via";
+    break;
+  case 5:
+    s = "Via Hidden";
+    break;
+  case 6:
+    switch (p->turn_type) {
+    case 1:
+      s = "Turn, Straight";
+      break;
+    case 2:
+      s = "Turn, Right";
+      break;
+    case 3:
+      s = "Turn, Bear Right";
+      break;
+    case 4:
+      s = "Turn, Keep Right";
+      break;
+    case 5:
+      s = "Turn, Left";
+      break;
+    case 6:
+      s = "Turn, Bear Left";
+      break;
+    case 7:
+      s = "Turn, Keep Left";
+      break;
+    case 8:
+      s = "Turn, Reverse Direction";
+      break;
+    case 9:
+      s = "Turn, Street Name Change";
+      break;
+    }
+    break;
+  }
+  if (s) {
+    gbfprintf(fd, "Type: %s", s);
+  }
+  if (p->exit_label_size && p->exit_label[0]) {
+    gbfprintf(fd, "\nExit: %s", p->exit_label);
+  }
+  s = p->exit_label + p->exit_label_size;
+  if (s[0] && s[1]) {
+    gbfprintf(fd, "\n%s", s + 1);
+  }
+  if (fd->memlen) {
+    gbfputc(0, fd);
+#if NEW_STRINGS
+    // Reconsider if there's a less grubby way to do this.
+    wp->notes = QString::fromUtf8((const char*) fd->handle.mem, fd->memlen);
+#else
+    wp->notes = (char*) xmalloc(fd->memlen);
+    memcpy(wp->notes, fd->handle.mem, fd->memlen);
+#endif
+  }
+  gbfclose(fd);
+  return wp;
+}
+
+static void
+read_route(route_head* route)
+{
+  message_t m;
+  message_t* msg_array;
+  const msg_route_header_t* p;
+  unsigned msg_array_n;
+  unsigned wp_array_i = 0;
+  unsigned route_total, shape_total, total;
+  unsigned i;
+  int attempt = ATTEMPT_MAX;
+
+  message_init(&m);
+  for (;;) {
+    m.size = MSG_REQUEST_ROUTES_SIZE;
+    memset(m.data, 0, m.size);
+    ((char*)m.data)[0] = 1;  // Download single route
+    strcpy((char*)m.data + 1, CSTRc(route->rte_name));
+    message_write(MSG_REQUEST_ROUTES, &m);
+    if (get_batch(&msg_array, &msg_array_n)) {
+      break;
+    }
+    if (--attempt == 0) {
+      fatal(MYNAME ": reading route '%s' failed (timed out)\n", qPrintable(route->rte_name));
+    }
+    if (global_opts.debug_level >= DBGLVL_M) {
+      warning(MYNAME ": timed out reading route route '%s', retrying\n", qPrintable(route->rte_name));
+    }
+    m.size = MSG_BREAK_SIZE;
+    memset(m.data, 0, m.size);
+    message_write(MSG_BREAK, &m);
+  }
+  message_free(&m);
+  if (msg_array_n == 0 || message_get_id(&msg_array[0]) != MSG_ROUTE_HEADER_OUT) {
+    fatal(MYNAME ": missing route header\n");
+  }
+  p = (const msg_route_header_t*) msg_array[0].data;
+  route_total = le_readu32(p->total_route_point);
+  shape_total = le_readu32(p->total_shape_point);
+  total = route_total + shape_total;
+  wp_array = (Waypoint**) xcalloc(total, sizeof(*wp_array));
+  if (global_opts.debug_level >= DBGLVL_L) {
+    warning(MYNAME ": route '%s' %u points, %u shape points\n",
+            qPrintable(route->rte_name), route_total, shape_total);
+  }
+  message_free(&msg_array[0]);
+  for (i = 1; i < msg_array_n; i++) {
+    unsigned id = message_get_id(&msg_array[i]);
+    if (id == MSG_ROUTE_POINT_OUT) {
+      wp_array[wp_array_i] = decode_route_point(msg_array[i].data);
+      if (global_opts.debug_level >= DBGLVL_L) {
+        warning(MYNAME ": route point '%s'\n", qPrintable(wp_array[wp_array_i]->shortname));
+      }
+      wp_array_i++;
+    } else if (id == MSG_ROUTE_SHAPE_OUT) {
+      decode_route_shape(msg_array[i].data, &wp_array_i);
+    } else {
+      fatal(MYNAME ": unexpected message %x while reading route '%s'\n", id, qPrintable(route->rte_name));
+    }
+    message_free(&msg_array[i]);
+  }
+  xfree(msg_array);
+  if (total != wp_array_i) {
+    fatal(MYNAME ": route point count mismatch, expected %u, got %u\n", total, wp_array_i);
+  }
+  for (i = 0; i < total; i++) {
+    route_add_wpt(route, wp_array[i]);
+  }
+  xfree(wp_array);
+  route_add_head(route);
+}
+
+static void
+read_routes(void)
+{
+  message_t m;
+  message_t* msg_array;
+  unsigned msg_array_n;
+  route_head** route_array;
+  unsigned total;
+  unsigned i;
+  int attempt = ATTEMPT_MAX;
+
+  message_init(&m);
+  // get number of routes
+  for (;;) {
+    m.size = 0;
+    message_write(MSG_ROUTE_COUNT, &m);
+    if (message_read(MSG_ROUTE_COUNT, &m)) {
+      break;
+    }
+    if (--attempt == 0) {
+      fatal(MYNAME ": reading route count failed\n");
+    }
+  }
+  total = le_readu32(m.data);
+  if (global_opts.debug_level >= DBGLVL_L) {
+    warning(MYNAME ": %u routes\n", total);
+  }
+  if (total == 0) {
+    message_free(&m);
+    return;
+  }
+
+  // First get route headers, then request each route
+  attempt = ATTEMPT_MAX;
+  for (;;) {
+    m.size = MSG_REQUEST_ROUTES_SIZE;
+    memset(m.data, 0, m.size);
+    ((char*)m.data)[0] = 2;  // Download all route headers
+    message_write(MSG_REQUEST_ROUTES, &m);
+    if (get_batch(&msg_array, &msg_array_n)) {
+      break;
+    }
+    if (--attempt == 0) {
+      fatal(MYNAME ": reading route headers failed\n");
+    }
+    if (global_opts.debug_level >= DBGLVL_M) {
+      warning(MYNAME ": timed out reading route headers, retrying\n");
+    }
+    m.size = MSG_BREAK_SIZE;
+    memset(m.data, 0, m.size);
+    message_write(MSG_BREAK, &m);
+  }
+  message_free(&m);
+  route_array = (route_head**) xcalloc(total, sizeof(*route_array));
+  for (i = 0; i < msg_array_n; i++) {
+    unsigned id = message_get_id(&msg_array[i]);
+    if (id == MSG_ROUTE_HEADER_OUT) {
+      route_array[i] = route_head_alloc();
+      route_array[i]->rte_name = ((msg_route_header_t*)msg_array[i].data)->name;
+    } else {
+      fatal(MYNAME ": unexpected message %x while reading route headers\n", id);
+    }
+    message_free(&msg_array[i]);
+  }
+  xfree(msg_array);
+  // get each route
+  for (i = 0; i < total; i++) {
+    read_route(route_array[i]);
+  }
+  xfree(route_array);
+}
+
+//-----------------------------------------------------------------------------
+// Route writing
+
+static unsigned route_point_n;
+static unsigned shape_point_n;
+static unsigned* shape_point_counts;
+
+static void
+write_route_shape_points(Waypoint** array, unsigned n)
+{
+  message_t m;
+  const unsigned pt_per_msg = 25;
+  msg_route_shape_t* p = NULL;
+  unsigned i = 0;
+  unsigned j = 0;
+
+  do {
+    if (j == 0) {
+      message_init_size(&m, 10 + 8 * pt_per_msg);
+      p = (msg_route_shape_t*) m.data;
+      le_write32(p->total, n);
+      le_write32(p->index, i + 1);
+      p->reserved = 0;
+    }
+    assert(p);
+    le_write32(p->point[j].latitude, delbin_deg2rad(array[i]->latitude));
+    le_write32(p->point[j].longitude, delbin_deg2rad(array[i]->longitude));
+    i++;
+    j++;
+    if (j == pt_per_msg || i == n) {
+      p->number = j;
+      m.size = 10 + 8 * j;
+      add_to_batch(MSG_ROUTE_SHAPE_IN, &m);
+      j = 0;
+    }
+  } while (i < n);
+}
+
+static void
+write_route_points(void)
+{
+  unsigned route_point_i = 0;
+  unsigned i = 0;
+
+  while (i < waypoint_n) {
+    message_t m;
+    unsigned shape_n;
+    const Waypoint* wp = wp_array[i];
+    msg_route_point_t* p;
+    char* s;
+
+    message_init_size(&m, sizeof(msg_route_point_t) + 1 + 1 + 4);
+    p = (msg_route_point_t*) m.data;
+    memset(m.data, 0, m.size);
+    route_point_i++;
+    shape_n = shape_point_counts[route_point_i];
+    le_write32(p->total, route_point_n);
+    le_write32(p->index, route_point_i);
+    if (!wp->shortname.isEmpty()) {
+      strncpy(p->name, CSTRc(wp->shortname), sizeof(p->name) - 1);
+    } else {
+      sprintf(p->name, "RPT%u", route_point_i);
+    }
+    le_write32(p->latitude, delbin_deg2rad(wp->latitude));
+    le_write32(p->longitude, delbin_deg2rad(wp->longitude));
+    p->exit_label_size = 1;
+    s = p->exit_label + p->exit_label_size;
+    s[0] = 1;  // comment size
+    le_write32(s + 2, shape_n);
+    if (route_point_i == 1) {
+      p->itinerary_type = 1; // start
+    } else if (route_point_i == route_point_n) {
+      p->itinerary_type = 3; // finish
+    }
+    add_to_batch(MSG_ROUTE_POINT_IN, &m);
+    i++;
+    if (shape_n) {
+      write_route_shape_points(&wp_array[i], shape_n);
+      i += shape_n;
+    }
+  }
+}
+
+static void
+write_route_begin(const route_head* track)
+{
+  waypoint_i = 0;
+  route_point_n = 0;
+  shape_point_n = 0;
+  waypoint_n = track->rte_waypt_ct;
+  if (waypoint_n) {
+    wp_array = (Waypoint**) xmalloc(waypoint_n * sizeof(*wp_array));
+    shape_point_counts = (unsigned int*) xcalloc(waypoint_n, sizeof(*shape_point_counts));
+  }
+}
+
+static void
+write_route_point(const Waypoint* wp)
+{
+  wp_array[waypoint_i++] = (Waypoint*)wp;
+  if (wp->shortname.startsWith("SHP")) {
+    shape_point_n++;
+    shape_point_counts[route_point_n]++;
+  } else {
+    route_point_n++;
+  }
+}
+
+static void
+write_route_end(const route_head* route)
+{
+  message_t m;
+  msg_route_header_in_t* p;
+
+  if (waypoint_n == 0) {
+    return;
+  }
+  message_init_size(&m, sizeof(msg_route_header_in_t));
+  p = (msg_route_header_in_t*) m.data;
+  memset(p->name, 0, sizeof(p->name));
+  if (!route->rte_name.isEmpty()) {
+    strncpy(p->name, CSTRc(route->rte_name), sizeof(p->name) - 1);
+  } else {
+    sprintf(p->name, "%lu", (long)wp_array[0]->GetCreationTime().toTime_t());
+  }
+  p->type = 0;
+  le_write32(p->total_route_point, route_point_n);
+  le_write32(p->total_shape_point, shape_point_n);
+  add_to_batch(MSG_ROUTE_HEADER_IN, &m);
+  write_route_points();
+  send_batch();
+  if (wp_array) {
+    xfree(wp_array);
+    xfree(shape_point_counts);
+  }
+}
+
+static void
+write_routes(void)
+{
+  route_disp_all(write_route_begin, write_route_end, write_route_point);
+}
+
+//-----------------------------------------------------------------------------
+// Current position
+
+static Waypoint*
+decode_navmsg(const void* data)
+{
+  Waypoint* wp = new Waypoint;
+  const msg_navigation_t* p = (const msg_navigation_t*) data;
+  struct tm t;
+
+  t.tm_year = le_readu16(p->year) - 1900;
+  t.tm_mon = p->month - 1;
+  t.tm_mday = p->day;
+  t.tm_hour = p->hour;
+  t.tm_min = p->minute;
+  t.tm_sec = p->second;
+  wp->SetCreationTime(mkgmtime(&t));
+  wp->sat = p->satellites;
+  wp->latitude = le_read_double(p->latitude);
+  wp->longitude = le_read_double(p->longitude);
+  wp->altitude = le_read_double(p->elevation);
+  wp->speed = le_read_float(p->speed);
+  wp->speed *= (1000.0f / (60 * 60));
+  wp->wpt_flags.speed = 1;
+  wp->course = le_readu16(p->heading);
+  wp->course /= 100;
+  wp->wpt_flags.course = 1;
+  decode_sat_fix(wp, p->fix_status);
+  wp->shortname = "Position";
+  return wp;
+}
+
+static Waypoint*
+read_position(void)
+{
+  Waypoint* wp;
+  message_t m;
+
+  message_init(&m);
+  message_read(MSG_NAVIGATION, &m);
+  wp = decode_navmsg(m.data);
+  if (wp->fix > fix_none &&
+      message_read_1(MSG_SATELLITE_INFO, &m) == MSG_SATELLITE_INFO) {
+    const msg_satellite_t* p = (const msg_satellite_t*) m.data;
+    wp->hdop = le_readu16(p->hdop);
+    wp->hdop /= 100;
+    wp->vdop = le_readu16(p->vdop);
+    wp->vdop /= 100;
+    wp->pdop = le_readu16(p->pdop);
+    wp->pdop /= 100;
+  }
+  message_free(&m);
+  return wp;
+}
+
+//-----------------------------------------------------------------------------
+
+static void
+delbin_list_units()
+{
+  int i;
+  for (i = 0; i < n_delbin_units; i++) {
+    printf("%u %s %s\n",
+           delbin_unit_info[i].unit_number,
+           delbin_unit_info[i].unit_serial_number,
+           delbin_unit_info[i].unit_name);
+  }
+}
+
+static void
+delbin_rw_init(const QString& fname)
+{
+  message_t m;
+  char buf[256];
+
+  if (!mkshort_handle) {
+    mkshort_handle = mkshort_new_handle();
+  }
+  //  Contrary to doc, it looks like there's a limit of 32 bytes
+  // and a null terminator is required, at least in F/W 2.6.210726
+  // on a PN-40.
+  setshort_length(mkshort_handle, 31);
+  setshort_whitespace_ok(mkshort_handle, 1);
+  setshort_badchars(mkshort_handle, "");
+  setshort_mustuniq(mkshort_handle, 1);
+
+  delbin_os_ops.init(fname);
+
+  // Often the first packet is part of an old message, sometimes it can
+  // confuse the first message read if we don't get rid of it
+  packet_read(buf);
+  // Send a break to clear any state from a previous failure
+  message_init_size(&m, MSG_BREAK_SIZE);
+  memset(m.data, 0, m.size);
+  message_write(MSG_BREAK, &m);
+  // get version info
+  m.size = 0;
+  message_write(MSG_VERSION, &m);
+  if (message_read(MSG_VERSION, &m)) {
+    const msg_version_t* p = (const msg_version_t*) m.data;
+    if (global_opts.debug_level >= DBGLVL_L) {
+      warning(MYNAME ": device %s %s\n", p->product, p->firmware);
+    }
+    if (opt_long_notes) {
+      use_extended_notes = TRUE;
+    } else if (strstr(p->product, "PN-20")) {
+      use_extended_notes = p->firmware[0] > '1' ||
+                           (p->firmware[0] == '1' && p->firmware[2] >= '6');
+    } else if (strstr(p->product, "PN-30") || strstr(p->product, "PN-40")) {
+      use_extended_notes = p->firmware[0] > '2' ||
+                           (p->firmware[0] == '2' && p->firmware[2] >= '5');
+    } else {
+      // assume PN-60 or later
+      use_extended_notes = TRUE;
+    }
+    delbin_unit_info[n_delbin_units].unit_number = n_delbin_units;
+    delbin_unit_info[n_delbin_units].unit_serial_number = xstrndup(p->serial, sizeof(p->serial));
+    delbin_unit_info[n_delbin_units].unit_name = xstrndup(p->product, sizeof(p->product));
+    n_delbin_units++;
+  }
+  message_free(&m);
+
+  if (fname.length() > 4) {
+    if (fname.mid(4,4) == "list") {
+      delbin_list_units();
+      exit(1);
+    }
+  }
+}
+
+static void
+delbin_rw_deinit(void)
+{
+  if (mkshort_handle) {
+    mkshort_del_handle(&mkshort_handle);
+  }
+  delbin_os_ops.deinit();
+}
+
+static void
+delbin_read(void)
+{
+  if (doing_wpts) {
+    if (opt_getposn) {
+      waypt_add(read_position());
+    } else {
+      read_waypoints();
+    }
+  }
+  if (doing_trks) {
+    read_tracks();
+  }
+  if (doing_rtes) {
+    read_routes();
+  }
+}
+
+static void
+delbin_write(void)
+{
+  if (doing_wpts) {
+    message_t m;
+    device_max_waypoint = 1000;
+    message_init_size(&m, 0);
+    message_write(MSG_CAPABILITIES, &m);
+    if (message_read(MSG_CAPABILITIES, &m)) {
+      const msg_capabilities_t* p = (const msg_capabilities_t*) m.data;
+      device_max_waypoint = le_readu32(p->max_waypoints);
+    }
+    message_free(&m);
+
+    if (opt_nuke_wpt) {
+      add_nuke(nuke_type_wpt);
+    }
+    write_waypoints();
+  }
+  if (doing_trks) {
+    if (opt_nuke_trk) {
+      add_nuke(nuke_type_trk);
+    }
+    write_tracks();
+  }
+  if (doing_rtes) {
+    if (opt_nuke_rte) {
+      add_nuke(nuke_type_rte);
+    }
+    write_routes();
+  }
+}
+
+static Waypoint*
+delbin_rd_position(posn_status* status)
+{
+  return read_position();
+}
+
+ff_vecs_t delbin_vecs = {
+  ff_type_serial,
+  FF_CAP_RW_ALL,
+  delbin_rw_init,
+  delbin_rw_init,
+  delbin_rw_deinit,
+  delbin_rw_deinit,
+  delbin_read,
+  delbin_write,
+  NULL,
+  delbin_args,
+  CET_CHARSET_LATIN1, 1,
+  { delbin_rw_init, delbin_rd_position, delbin_rw_deinit }
+};
+
+//=============================================================================
+// OS device I/O implementations
+
+#define VENDOR_ID 0x1163
+#define PRODUCT_ID 0x2020
+
+//-----------------------------------------------------------------------------
+// Windows
+#ifdef HAVE_WDK
+
+#undef HAVE_LIBUSB
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <setupapi.h>
+// If hidsdi.h is not found, you need to download the Windows Driver Kit,
+// from http://www.microsoft.com/whdc/Devtools/wdk/default.mspx
+// You need to install 'build environments' and 'tools' from the SDK and
+// follow the instructions in the Install.html to get MSVC to find the right
+// headers and libraries.
+#include <specstrings.h>
+#include <hidsdi.h>
+
+static HANDLE hid_handle;
+
+static void
+win_os_init(const QString& fname)
+{
+  GUID hid_guid;
+  HDEVINFO dev_info;
+  SP_DEVICE_INTERFACE_DATA dev_int_data;
+  PHIDP_PREPARSED_DATA hid_ppd;
+  HIDP_CAPS hid_caps;
+  const char* busy = "";
+  unsigned i;
+
+  hid_handle = INVALID_HANDLE_VALUE;
+  HidD_GetHidGuid(&hid_guid);
+  dev_info = SetupDiGetClassDevs(&hid_guid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
+  if (dev_info == INVALID_HANDLE_VALUE) {
+    fatal(MYNAME ": SetupDiGetClassDevs failed %u\n", GetLastError());
+  }
+  dev_int_data.cbSize = sizeof(dev_int_data);
+  for (i = 0; SetupDiEnumDeviceInterfaces(dev_info, NULL, &hid_guid, i, &dev_int_data); i++) {
+    union {
+      SP_DEVICE_INTERFACE_DETAIL_DATA detail_data;
+      char buf[300];
+    } u;
+    u.detail_data.cbSize = sizeof(u.detail_data);
+    if (SetupDiGetDeviceInterfaceDetail(dev_info,
+                                        &dev_int_data, &u.detail_data, sizeof(u.buf), NULL, NULL)) {
+      HANDLE h = CreateFile(u.detail_data.DevicePath,
+                            FILE_READ_DATA | FILE_WRITE_DATA, 0, NULL, OPEN_EXISTING, 0, NULL);
+      if (h != INVALID_HANDLE_VALUE) {
+        HIDD_ATTRIBUTES hid_attr;
+        hid_attr.Size = sizeof(hid_attr);
+        if (HidD_GetAttributes(h, &hid_attr) &&
+            hid_attr.VendorID == VENDOR_ID && hid_attr.ProductID == PRODUCT_ID) {
+          hid_handle = h;
+          break;
+        }
+        CloseHandle(h);
+      } else if (GetLastError() == ERROR_SHARING_VIOLATION &&
+                 strstr(u.detail_data.DevicePath, "1163") &&
+                 strstr(u.detail_data.DevicePath, "2020")) {
+        busy = " (device busy?)";
+      }
+    }
+  }
+  SetupDiDestroyDeviceInfoList(dev_info);
+  if (hid_handle == INVALID_HANDLE_VALUE) {
+    fatal(MYNAME ": no DeLorme PN found%s\n", busy);
+  }
+  if (!HidD_GetPreparsedData(hid_handle, &hid_ppd)) {
+    fatal(MYNAME ": HidD_GetPreparsedData failed %u\n", GetLastError());
+  }
+  if (!HidP_GetCaps(hid_ppd, &hid_caps)) {
+    fatal(MYNAME ": HidP_GetCaps failed %u\n", GetLastError());
+  }
+  // report length includes report id
+  delbin_os_packet_size = hid_caps.InputReportByteLength - 1;
+  HidD_FreePreparsedData(hid_ppd);
+}
+
+static void
+win_os_deinit(void)
+{
+  CloseHandle(hid_handle);
+}
+
+static unsigned
+win_os_packet_read(void* buf)
+{
+  DWORD n;
+  char buf1[257];
+  // first byte is report id
+  if (ReadFile(hid_handle, buf1, delbin_os_packet_size + 1, &n, NULL) == 0) {
+    unsigned err = GetLastError();
+    fatal(MYNAME ": ReadFile failed %u\n", err);
+  }
+  if (n > 0) {
+    n--;
+  }
+  memcpy(buf, buf1 + 1, n);
+  return n;
+}
+
+static unsigned
+win_os_packet_write(const void* buf, unsigned size)
+{
+  DWORD n;
+  char buf1[257];
+  // first byte is report id
+  buf1[0] = 0;
+  memcpy(buf1 + 1, buf, size);
+  if (WriteFile(hid_handle, buf1, delbin_os_packet_size + 1, &n, NULL) == 0) {
+    unsigned err = GetLastError();
+    fatal(MYNAME ": WriteFile of %u bytes failed with %u.  Size: %u Wrote: %d\n",
+          delbin_os_packet_size + 1, err, size, (int) n);
+  }
+  if (n > size) {
+    n = size;
+  }
+  return n;
+}
+
+delbin_os_ops_t delbin_os_ops = {
+  win_os_init,
+  win_os_deinit,
+  win_os_packet_read,
+  win_os_packet_write
+};
+
+#endif // HAVE_WDK
+
+//-----------------------------------------------------------------------------
+// MacOS X
+#if __APPLE__
+
+#undef HAVE_LIBUSB
+
+#include <pthread.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/IOCFPlugIn.h>
+#include <IOKit/hid/IOHIDLib.h>
+#include <mach/mach_error.h>
+
+// IOHIDDeviceInterface121::getReport() does not work, it hangs the process
+// in some sort of unkillable state.  So reading is done via a separate thread
+// with a run loop and interrupt report callback. Yuck.
+
+static IOHIDDeviceInterface122** device;
+static pthread_t thread;
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+static char* report_buf;
+static char* packet_array[32];
+static unsigned packet_array_head;
+static unsigned packet_array_tail;
+static CFRunLoopRef run_loop;
+
+static void*
+thread_func(void* run_loop_source)
+{
+  run_loop = CFRunLoopGetCurrent();
+#if __cplusplus
+  CFRunLoopAddSource(run_loop, (__CFRunLoopSource*) run_loop_source, kCFRunLoopDefaultMode);
+#else
+  CFRunLoopAddSource(run_loop, run_loop_source, kCFRunLoopDefaultMode);
+#endif
+  CFRunLoopRun();
+  return NULL;
+}
+
+static void
+interrupt_report_cb(void* target, IOReturn result, void* refcon, void* sender, UInt32 bufferSize)
+{
+  memcpy(packet_array[packet_array_head], report_buf, delbin_os_packet_size);
+  pthread_mutex_lock(&mutex);
+  if (packet_array_head == packet_array_tail) {
+    pthread_cond_signal(&cond);
+  }
+  packet_array_head++;
+  packet_array_head &= sizeofarray(packet_array) - 1;
+  if (packet_array_head == packet_array_tail && global_opts.debug_level >= DBGLVL_M) {
+    warning(MYNAME ": packet_array overrun, packets lost\n");
+  }
+  pthread_mutex_unlock(&mutex);
+}
+
+static void
+mac_os_init(const QString& fname)
+{
+  CFMutableDictionaryRef dict = IOServiceMatching(kIOHIDDeviceKey);
+  io_service_t service;
+  IOCFPlugInInterface** plugin;
+  CFNumberRef cf_num;
+  CFRunLoopSourceRef run_loop_source;
+  int i;
+  kern_return_t kr;
+  HRESULT hr;
+  IOReturn ir;
+  SInt32 unused;
+
+  i = VENDOR_ID;
+  cf_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &i);
+  CFDictionaryAddValue(dict, CFSTR(kIOHIDVendorIDKey), cf_num);
+  CFRelease(cf_num);
+  i = PRODUCT_ID;
+  cf_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &i);
+  CFDictionaryAddValue(dict, CFSTR(kIOHIDProductIDKey), cf_num);
+  CFRelease(cf_num);
+  service = IOServiceGetMatchingService(kIOMasterPortDefault, dict);
+  if (service == 0) {
+    fatal(MYNAME ": no DeLorme PN found\n");
+  }
+  kr = IOCreatePlugInInterfaceForService(
+         service, kIOHIDDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &unused);
+  if (kr) {
+    fatal(MYNAME ": IOCreatePlugInInterfaceForService failed 0x%x\n", (int)kr);
+  }
+  IOObjectRelease(service);
+  hr = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID122), (void**)&device);
+  if (hr) {
+    fatal(MYNAME ": QueryInterface failed 0x%x\n", (int)hr);
+  }
+  (*plugin)->Release(plugin);
+  ir = (*device)->open(device, kIOHIDOptionsTypeSeizeDevice);
+  if (ir)
+    fatal(MYNAME ": device open failed 0x%x - %s\n", (int)ir,
+          mach_error_string(ir));
+  ir = (*device)->createAsyncEventSource(device, &run_loop_source);
+  if (ir) {
+    fatal(MYNAME ": createAsyncEventSource failed 0x%x\n", (int)ir);
+  }
+  delbin_os_packet_size = 64;
+  report_buf = (char*)xmalloc(delbin_os_packet_size);
+  for (i = sizeofarray(packet_array); i--;) {
+    packet_array[i] = (char*)xmalloc(delbin_os_packet_size);
+  }
+  ir = (*device)->setInterruptReportHandlerCallback(
+         device, report_buf, delbin_os_packet_size, interrupt_report_cb, NULL, NULL);
+  if (ir) {
+    fatal(MYNAME ": setInterruptReportHandlerCallback failed 0x%x\n", (int)ir);
+  }
+  i = pthread_create(&thread, NULL, thread_func, run_loop_source);
+  if (i) {
+    fatal(MYNAME ": pthread_create failed %d\n", i);
+  }
+}
+
+static void
+mac_os_deinit(void)
+{
+  void* unused;
+  unsigned i;
+  CFRunLoopStop(run_loop);
+  pthread_join(thread, &unused);
+  (*device)->Release(device);
+  xfree(report_buf);
+  for (i = sizeofarray(packet_array); i--;) {
+    xfree(packet_array[i]);
+  }
+}
+
+static unsigned
+mac_os_packet_read(void* buf)
+{
+  pthread_mutex_lock(&mutex);
+  while (packet_array_head == packet_array_tail) {
+    pthread_cond_wait(&cond, &mutex);
+  }
+  memcpy(buf, packet_array[packet_array_tail++], delbin_os_packet_size);
+  packet_array_tail &= sizeofarray(packet_array) - 1;
+  pthread_mutex_unlock(&mutex);
+  return delbin_os_packet_size;
+}
+
+static unsigned
+mac_os_packet_write(const void* buf, unsigned size)
+{
+  IOReturn r = (*device)->setReport(
+                 device, kIOHIDReportTypeOutput, 0, (void*)buf, size, 2000, NULL, NULL, NULL);
+  if (r) {
+    fatal("setReport failed 0x%x\n", (int)r);
+  }
+  return size;
+}
+
+delbin_os_ops_t delbin_os_ops = {
+  mac_os_init,
+  mac_os_deinit,
+  mac_os_packet_read,
+  mac_os_packet_write
+};
+
+#endif // __APPLE__
+
+//-----------------------------------------------------------------------------
+// libusb
+#if HAVE_LIBUSB
+
+#include <usb.h>
+
+static struct usb_device* usb_dev;
+static usb_dev_handle* usb_handle;
+static int endpoint_in;
+static int endpoint_out;
+
+static void
+libusb_os_init(const QString& fname)
+{
+  struct usb_bus* bus;
+  const struct usb_endpoint_descriptor* endpoint_desc;
+
+  usb_init();
+  usb_find_busses();
+  usb_find_devices();
+  for (bus = usb_busses; usb_dev == NULL && bus; bus = bus->next) {
+    struct usb_device* d;
+    for (d = bus->devices; d; d = d->next) {
+      if (d->descriptor.idVendor == VENDOR_ID && d->descriptor.idProduct == PRODUCT_ID) {
+        usb_dev = d;
+        break;
+      }
+    }
+  }
+  if (usb_dev == NULL) {
+    fatal(MYNAME ": no DeLorme PN found\n");
+  }
+  usb_handle = usb_open(usb_dev);
+  if (usb_handle == NULL) {
+    fatal(MYNAME ": %s\n", usb_strerror());
+  }
+
+  // Device has 1 configuration, 1 interface, 2 interrupt endpoints
+  if (usb_claim_interface(usb_handle, 0) < 0) {
+#if LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
+    if (usb_detach_kernel_driver_np(usb_handle, 0) < 0) {
+      warning(MYNAME ": %s\n", usb_strerror());
+    }
+    if (usb_claim_interface(usb_handle, 0) < 0)
+#endif
+    {
+      const char* s = usb_strerror();
+      usb_close(usb_handle);
+      fatal(MYNAME ": %s\n", s);
+    }
+  }
+  endpoint_desc = usb_dev->config[0].interface[0].altsetting[0].endpoint;
+  delbin_os_packet_size = endpoint_desc[0].wMaxPacketSize;
+  endpoint_in = endpoint_desc[0].bEndpointAddress;
+  endpoint_out = endpoint_desc[1].bEndpointAddress;
+  if ((endpoint_in & USB_ENDPOINT_DIR_MASK) == USB_ENDPOINT_OUT) {
+    int t = endpoint_in;
+    endpoint_in = endpoint_out;
+    endpoint_out = t;
+  }
+}
+
+static void
+libusb_os_deinit(void)
+{
+  usb_release_interface(usb_handle, 0);
+  usb_close(usb_handle);
+}
+
+static unsigned
+libusb_os_packet_read(void* buf)
+{
+  int n = usb_interrupt_read(usb_handle, endpoint_in, (char*) buf, delbin_os_packet_size, 2000);
+  if (n < 0) {
+    fatal(MYNAME ": %s\n", usb_strerror());
+  }
+  return n;
+}
+
+static unsigned
+libusb_os_packet_write(const void* buf, unsigned size)
+{
+  int n = usb_interrupt_write(usb_handle, endpoint_out, (char*)buf, size, 2000);
+  if (n < 0) {
+    fatal(MYNAME ": %s\n", usb_strerror());
+  }
+  return n;
+}
+
+#if HAVE_LINUX_HID
+static const delbin_os_ops_t libusb_os_ops =
+#else
+delbin_os_ops_t delbin_os_ops =
+#endif
+{
+  libusb_os_init,
+  libusb_os_deinit,
+  libusb_os_packet_read,
+  libusb_os_packet_write
+};
+
+#endif // HAVE_LIBUSB
+
+//-----------------------------------------------------------------------------
+// Linux
+#if HAVE_LINUX_HID
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <linux/types.h>
+#include <linux/hiddev.h>
+#include <linux/hidraw.h>
+
+// Read from hidraw, write to hiddev. Reading from hiddev does not work,
+// and neither does writing to hidraw.
+static int fd_hidraw;
+static int fd_hiddev;
+
+static int linuxhid_os_init_status;
+
+static void
+linuxhid_os_init(const QString& fname)
+{
+  struct hidraw_devinfo info;
+  struct hiddev_field_info finfo;
+  DIR* dir = NULL;
+  struct dirent* d;
+
+  fd_hidraw = fd_hiddev = -1;
+  if (fname.startsWith("hid:")) {
+    char* raw_name = xstrdup(qPrintable(fname.mid(4)));
+    char* dev_name = strchr(raw_name, ',');
+    if (dev_name == NULL) {
+      fatal(MYNAME ": missing hiddev path\n");
+    }
+    *dev_name++ = 0;
+    fd_hidraw = open(raw_name, O_RDONLY);
+    if (fd_hidraw < 0) {
+      fatal(MYNAME ": %s: %s\n", raw_name, strerror(errno));
+    }
+    fd_hiddev = open(dev_name, O_WRONLY);
+    if (fd_hiddev < 0) {
+      fatal(MYNAME ": %s: %s\n", dev_name, strerror(errno));
+    }
+    xfree(raw_name);
+  } else {
+    dir = opendir("/dev");
+  }
+  while (dir && (d = readdir(dir)) != NULL) {
+    if (strncmp(d->d_name, "hidraw", 6) == 0) {
+      int fd1, fd2;
+      char raw_name[32];
+      char dev_name[32];
+      sprintf(raw_name, "/dev/%s", d->d_name);
+      fd1 = open(raw_name, O_RDONLY);
+      if (fd1 < 0) {
+        if (global_opts.debug_level >= DBGLVL_M) {
+          warning(MYNAME ": %s: %s\n", raw_name, strerror(errno));
+        }
+        continue;
+      }
+      sprintf(dev_name, "/dev/usb/hiddev%s", raw_name + sizeof("/dev/hidraw") - 1);
+      fd2 = open(dev_name, O_WRONLY);
+      if (fd2 < 0 && errno == ENOENT) {
+        sprintf(dev_name, "/dev/hiddev%s", raw_name + sizeof("/dev/hidraw") - 1);
+        fd2 = open(dev_name, O_WRONLY);
+      }
+      if (fd2 < 0) {
+        if (global_opts.debug_level >= DBGLVL_M) {
+          warning(MYNAME ": %s: %s\n", dev_name, strerror(errno));
+        }
+        close(fd1);
+        continue;
+      }
+      if (ioctl(fd1, HIDIOCGRAWINFO, &info) == 0 &&
+          info.vendor == VENDOR_ID && info.product == PRODUCT_ID) {
+        fd_hidraw = fd1;
+        fd_hiddev = fd2;
+        break;
+      }
+      close(fd1);
+      close(fd2);
+    }
+  }
+  if (dir) {
+    closedir(dir);
+  }
+  if (fd_hidraw < 0) {
+    if (linuxhid_os_init_status == 0) {
+      fatal(MYNAME ": no DeLorme PN found\n");
+    }
+    return;
+  }
+  finfo.report_type = HID_REPORT_TYPE_INPUT;
+  finfo.report_id = 0;
+  finfo.field_index = 0;
+  if (ioctl(fd_hiddev, HIDIOCGFIELDINFO, &finfo) < 0) {
+    warning(MYNAME ": HIDIOCGFIELDINFO: %s\n", strerror(errno));
+    if (linuxhid_os_init_status == 0) {
+      exit(1);
+    }
+    return;
+  }
+  delbin_os_packet_size = finfo.maxusage;
+  linuxhid_os_init_status = 0;
+}
+
+static void
+linuxhid_os_deinit(void)
+{
+  close(fd_hidraw);
+  close(fd_hiddev);
+}
+
+static unsigned
+linuxhid_os_packet_read(void* buf)
+{
+  int n = read(fd_hidraw, buf, delbin_os_packet_size);
+  if (n < 0) {
+    fatal(MYNAME ": %s\n", strerror(errno));
+  }
+  return n;
+}
+
+static unsigned
+linuxhid_os_packet_write(const void* buf, unsigned size)
+{
+  struct hiddev_usage_ref_multi urefm;
+  struct hiddev_report_info rinfo;
+  const uint8_t* p = (const uint8_t*) buf;
+  unsigned i;
+
+  for (i = 0; i < size; i++) {
+    urefm.values[i] = p[i];
+  }
+  urefm.num_values = size;
+  memset(&urefm.uref, 0, sizeof(urefm.uref));
+  urefm.uref.report_type = HID_REPORT_TYPE_OUTPUT;
+  if (ioctl(fd_hiddev, HIDIOCSUSAGES, &urefm)) {
+    fatal(MYNAME ": HIDIOCSUSAGES: %s\n", strerror(errno));
+  }
+  memset(&rinfo, 0, sizeof(rinfo));
+  rinfo.report_type = HID_REPORT_TYPE_OUTPUT;
+  if (ioctl(fd_hiddev, HIDIOCSREPORT, &rinfo)) {
+    fatal(MYNAME ": HIDIOCSREPORT: %s\n", strerror(errno));
+  }
+  return size;
+}
+
+static const delbin_os_ops_t linuxhid_os_ops = {
+  linuxhid_os_init,
+  linuxhid_os_deinit,
+  linuxhid_os_packet_read,
+  linuxhid_os_packet_write
+};
+
+static void
+linux_os_init(const QString& fname)
+{
+  // tell linuxhid_os_init not to exit
+  linuxhid_os_init_status = 1;
+  linuxhid_os_init(fname);
+  if (linuxhid_os_init_status == 0) {
+    delbin_os_ops = linuxhid_os_ops;
+  } else {
+#if HAVE_LIBUSB
+    if (global_opts.debug_level >= DBGLVL_M) {
+      warning(MYNAME ": HID init failed, falling back to libusb\n");
+    }
+    delbin_os_ops = libusb_os_ops;
+    delbin_os_ops.init(fname);
+#else
+    fatal(MYNAME ": no DeLorme PN found\n");
+#endif
+  }
+}
+
+delbin_os_ops_t delbin_os_ops = {
+  linux_os_init,
+  NULL,
+  NULL,
+  NULL
+};
+
+#endif // HAVE_LINUX_HID
+
+//-----------------------------------------------------------------------------
+// stubs
+#if !(HAVE_WDK || __APPLE__ || HAVE_LIBUSB || HAVE_LINUX_HID)
+static void
+stub_os_init(const QString& fname)
+{
+  fatal(MYNAME ": OS not supported\n");
+}
+static void
+stub_os_deinit(void)
+{
+}
+static unsigned
+stub_os_packet_read(void* buf)
+{
+  return 0;
+}
+static unsigned
+stub_os_packet_write(const void* buf, unsigned size)
+{
+  return 0;
+}
+delbin_os_ops_t delbin_os_ops = {
+  stub_os_init,
+  stub_os_deinit,
+  stub_os_packet_read,
+  stub_os_packet_write
+};
+#endif
+// end OS device I/O implementations section
+//=============================================================================
+
+static const int track_color_bgr[] = {
+  0x0000ff, // red
+  0x00ffff, // yellow
+  0x008000, // green
+  0xff0000, // blue
+  0x808080, // gray
+  0xffffff, // white
+  0,        // black
+  0xffff00, // cyan
+  0xff00ff, // magenta
+  0x00a5ff, // orange
+  0x82004b, // indigo
+  0xeea5ee  // violet
+};
+
+static int track_color(unsigned i)
+{
+  int bgr = -1;
+  if (i < sizeofarray(track_color_bgr)) {
+    bgr = track_color_bgr[i];
+  }
+  return bgr;
+}
+
+static unsigned track_color_index(int bgr)
+{
+  unsigned i = sizeofarray(track_color_bgr);
+  do {
+    i--;
+  } while (i != 0 && track_color_bgr[i] != bgr);
+  return i;
+}
+
+static const char* const waypoint_symbol_name[] = {
+  // 0
+  "Red Map Pin",
+  "Dark Red Map Pin",
+  "Yellow Map Pin",
+  "Dark Yellow Map Pin",
+  "Green Map Pin",
+  "Dark Green Map Pin",
+  "Turquoise Map Pin",
+  "Dark Turquoise Map Pin",
+  "Blue Map Pin",
+  "Dark Blue Map Pin",
+  // 10
+  "Gray Map Pin",
+  "Dark Gray Map Pin",
+  "Red Flag",
+  "Dark Red Flag",
+  "Yellow Flag",
+  "Dark Yellow Flag",
+  "Green Flag",
+  "Dark Green Flag",
+  "Turquoise Flag",
+  "Dark Turquoise Flag",
+  // 20
+  "Blue Flag",
+  "Dark Blue Flag",
+  "Gray Flag",
+  "Dark Gray Flag",
+  "Red Dot",
+  "Dark Red Dot",
+  "Yellow Dot",
+  "Dark Yellow Dot",
+  "Green Dot",
+  "Dark Green Dot",
+  // 30
+  "Turquoise Dot",
+  "Dark Turquoise Dot",
+  "Blue Dot",
+  "Dark Blue Dot",
+  "Gray Dot",
+  "Dark Gray Dot",
+  "Small Red Dot",
+  "Small Dark Red Dot",
+  "Small Yellow Dot",
+  "Small Dark Yellow Dot",
+  // 40
+  "Small Green Dot",
+  "Small Dark Green Dot",
+  "Small Turquoise Dot",
+  "Small Dark Turquoise Dot",
+  "Small Blue Dot",
+  "Small Dark Blue Dot",
+  "Small Gray Dot",
+  "Small Dark Gray Dot",
+  "Arrow Up",
+  "Arrow Down",
+  // 50
+  "Arrow Left",
+  "Arrow Right",
+  "Arrow Up Left",
+  "Arrow Up Right",
+  "Arrow Down Left",
+  "Arrow Down Right",
+  "Green Star",
+  "Yellow Square",
+  "Red X",
+  "Turquoise Circle",
+  // 60
+  "Purple Triangle",
+  "American Flag",
+  "Stop",
+  "Parking",
+  "First Aid",
+  "Dining",
+  "Railroad Crossing",
+  "Heliport",
+  "Restroom",
+  "Information",
+  // 70
+  "Diver Down",
+  "Exit",
+  "Health Facility",
+  "Police",
+  "Post Office",
+  "Mining",
+  "Danger",
+  "Money",
+  "Exclamation",
+  "Car",
+  // 80
+  "Jeep",
+  "Truck",
+  "Tow Truck",
+  "Motor Home",
+  "School Bus",
+  "Four-wheeler",
+  "Snowmobile",
+  "Sailboat",
+  "Powerboat",
+  "Boat Launch",
+  // 90
+  "Anchor",
+  "Buoy",
+  "Shipwreck",
+  "Glider Area",
+  "Private Airport",
+  "Public Airport",
+  "Military Airport",
+  "Military Base",
+  "House",
+  "Church",
+  // 100
+  "Building",
+  "School",
+  "Lighthouse",
+  "Bridge",
+  "Radio Tower",
+  "Dam",
+  "Tunnel",
+  "Toll Booth",
+  "Gas Station",
+  "Lodging",
+  // 110
+  "Telephone",
+  "Traffic Light",
+  "Fire Hydrant",
+  "Cemetery",
+  "Picnic Table",
+  "Tent",
+  "Shelter",
+  "Camper",
+  "Fire",
+  "Shower",
+  // 120
+  "Drinking Water",
+  "Binoculars",
+  "Camera",
+  "Geocache",
+  "Geocache Found",
+  "Fishing Pole",
+  "Ice Fishing Trap Set",
+  "Ice Fishing Trap Up",
+  "Moose",
+  "Deer",
+  // 130
+  "Bear",
+  "Bird",
+  "Duck",
+  "Fish",
+  "Deer Tracks",
+  "Animal Tracks",
+  "Bird Tracks",
+  "Birch Tree",
+  "Evergreen Tree",
+  "Deciduous Tree",
+  // 140
+  "Flower Garden",
+  "Mountain",
+  "Cave",
+  "Beach",
+  "Hiking",
+  "Swimming",
+  "Bicycling",
+  "Kayaking",
+  "Canoeing",
+  "Water Skiing",
+  // 150
+  "Cross-country Skiing",
+  "Downhill Skiing",
+  "Ice Skating",
+  "Dogsledding",
+  "Shooting",
+  "Golf Course",
+  "Ballpark",
+  // 157-182 added in PN-40 2.5 firmware
+  "Cache Found",
+  "Didn't Find It",
+  "My Cache",
+  // 160
+  "Traditional Cache",
+  "Multi-Cache",
+  "Unknown Cache",
+  "Letterbox Hybrid",
+  "Whereigo Cache",
+  "Event Cache",
+  "Mega-Event Cache",
+  "Cache In Trash Out Event",
+  "EarthCache",
+  "Virtual Cache",
+  // 170
+  "Webcam Cache",
+  "Waymark",
+  "NGS Benchmark",
+  "Write Note",
+  "Needs Maintenance",
+  "Final Location",
+  "Parking Area",
+  "Question to Answer",
+  "Reference Point",
+  "Stages of a Multicache",
+  // 180
+  "Trailhead",
+  "Temporarily Disable Listing",
+  "Enable Listing",
+  // 183-222 added in PN-40 2.7 firmware
+  "Crane Truck",
+  "Forest Fire",
+  "Oil Derrick",
+  "Wind Turbine",
+  "Letter A",
+  "Letter B",
+  "Letter C",
+  // 190
+  "Letter D",
+  "Letter E",
+  "Letter F",
+  "Letter G",
+  "Letter H",
+  "Letter I",
+  "Letter J",
+  "Letter K",
+  "Letter L",
+  "Letter M",
+  // 200
+  "Letter N",
+  "Letter O",
+  "Letter P",
+  "Letter Q",
+  "Letter R",
+  "Letter S",
+  "Letter T",
+  "Letter U",
+  "Letter V",
+  "Letter W",
+  // 210
+  "Letter X",
+  "Letter Y",
+  "Letter Z",
+  "Numeral 0",
+  "Numeral 1",
+  "Numeral 2",
+  "Numeral 3",
+  "Numeral 4",
+  "Numeral 5",
+  "Numeral 6",
+  // 220
+  "Numeral 7",
+  "Numeral 8",
+  "Numeral 9"
+};
+
+static const char*
+waypoint_symbol(unsigned i)
+{
+  const char* p = NULL;
+  if (i < sizeofarray(waypoint_symbol_name)) {
+    p = waypoint_symbol_name[i];
+  }
+  return p;
+}
+
+static unsigned
+waypoint_symbol_index(const char* name)
+{
+  static unsigned last_result;
+  static char last_name[32];
+  unsigned i = last_result;
+
+  if (strncmp(name, last_name, sizeof(last_name)) != 0) {
+    i = sizeofarray(waypoint_symbol_name);
+    do {
+      i--;
+    } while (i != 0 && case_ignore_strcmp(name, waypoint_symbol_name[i]) != 0);
+    strncpy(last_name, name, sizeof(last_name));
+    last_result = i;
+  }
+  return i;
+}
+
+// vi: ts=4 sw=4 noexpandtab
diff --git a/deprecated/google.cc b/deprecated/google.cc
new file mode 100644 (file)
index 0000000..1f8a9b5
--- /dev/null
@@ -0,0 +1,556 @@
+/*
+    Copyright (C) 2002-2014 Robert Lipe, robertlipe+source@gpsbabel.org
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
+
+ */
+
+
+#include "defs.h"
+#include "xmlgeneric.h"
+#include <QtCore/QFile>
+#include <QtCore/QTextStream>
+#include <QtCore/QXmlStreamAttributes>
+
+static char* encoded_points = NULL;
+static char* encoded_levels = NULL;
+static QString script;
+static route_head** routehead;
+static int* routecount;
+static short_handle desc_handle;
+static QString rd_fname;
+
+static int serial = 0;
+
+#define MYNAME "google"
+
+static xg_callback      goog_points, goog_levels, goog_poly_e, goog_script;
+static xg_callback     goog_segment_s, goog_segment, goog_td_s, goog_td_b;
+static xg_callback     goog_td_e;
+
+static
+xg_tag_mapping google_map[] = {
+  { goog_points,  cb_cdata,       "/page/directions/polyline/points" },
+  { goog_levels,  cb_cdata,       "/page/directions/polyline/levels" },
+  { goog_poly_e,  cb_end,         "/page/directions/polyline" },
+  { goog_script,  cb_cdata,       "/html/head/script" },
+  { goog_segment_s, cb_start,      "/page/directions/segments/segment" },
+  { goog_segment, cb_cdata,      "/page/directions/segments/segment" },
+  { goog_td_s,    cb_start,      "/div/table/tr/td" },
+  { goog_td_s,    cb_start,      "/div/div/table/tr/td" },
+  { goog_td_b,      cb_cdata,      "/div/table/tr/td/b" },
+  { goog_td_b,      cb_cdata,      "/div/div/table/tr/td/b" },
+  { goog_td_e,    cb_end,        "/div/table/tr/td" },
+  { goog_td_e,    cb_end,        "/div/div/table/tr/td" },
+  { NULL, (xg_cb_type)0,              NULL }
+};
+
+void goog_script(xg_string args, const QXmlStreamAttributes*)
+{
+  script += args;
+}
+
+
+void goog_points(xg_string args, const QXmlStreamAttributes*)
+{
+  if (encoded_points) {
+    encoded_points = xstrappend(encoded_points, CSTRc(args));
+  } else {
+    encoded_points = xstrdup(args);
+  }
+}
+
+
+void goog_levels(xg_string args, const QXmlStreamAttributes*)
+{
+  if (encoded_levels) {
+    encoded_levels = xstrappend(encoded_levels, CSTRc(args));
+  } else {
+    encoded_levels = xstrdup(args);
+  }
+}
+
+static char goog_segname[7];
+static QString goog_realname;
+static int goog_segroute = 0;
+
+/*
+ * The segments contain an index into the points array.  We use that
+ * index to find the waypoint and insert a better name for it.
+ */
+void goog_segment_s(xg_string args, const QXmlStreamAttributes* attrv)
+{
+  QStringRef ptidx = attrv->value("pointIndex");
+  if (!ptidx.isEmpty()) {
+    snprintf(goog_segname, sizeof(goog_segname), "\\%5.5x",
+             ptidx.toString().toUInt());
+  }
+}
+
+void goog_segment(xg_string args, const QXmlStreamAttributes*)
+{
+  Waypoint* wpt_tmp;
+
+  if (routehead[goog_segroute]) {
+    wpt_tmp = route_find_waypt_by_name(routehead[goog_segroute], goog_segname);
+    if (wpt_tmp) {
+      wpt_tmp->shortname = mkshort(desc_handle, args);
+      wpt_tmp->description = args;
+    }
+  }
+}
+
+void goog_td_s(xg_string args, const QXmlStreamAttributes* attrv)
+{
+  bool isdesc = false, isseg = false;
+  QStringRef aclass = attrv->value("class");
+  QStringRef id = attrv->value("id");
+
+  if (aclass.isEmpty() || id.isEmpty()) {
+    return;
+  }
+
+  isdesc = (aclass == "desc");
+  isseg = (aclass == "dirsegtext");
+
+  if (isdesc) {
+    QStringRef subid(id.string(), id.position() + 6, id.length() - 6);
+
+    goog_segroute = 0;
+    snprintf(goog_segname, sizeof(goog_segname), "\\%5.5x",
+             subid.toString().toUInt());
+  } else if (isseg) {
+    QString idstr = id.toString();
+    int first_us;
+
+    goog_segroute = 0;
+
+    first_us = idstr.indexOf("_");
+    if (idstr.indexOf("_", first_us + 1) != -1) {
+      goog_segroute = idstr.mid(first_us + 1).toUInt();
+    }
+
+    snprintf(goog_segname, sizeof(goog_segname), "\\%5.5x",
+             idstr.mid(idstr.lastIndexOf("_") + 1).toUInt() +
+             routecount[goog_segroute]);
+  }
+}
+
+void goog_td_b(xg_string args, const QXmlStreamAttributes*)
+{
+  if (goog_segname[0] == '\\' && !strchr(CSTRc(args), '\xa0')) {
+    goog_realname = args;
+  }
+}
+
+void goog_td_e(xg_string args, const QXmlStreamAttributes*)
+{
+  if (goog_segname[0] == '\\' && !goog_realname.isEmpty()) {
+    goog_segment(goog_realname, NULL/*unused*/);
+  }
+  goog_segname[0] = '\0';
+  goog_realname.clear();
+}
+
+static long decode_goog64(char** str)
+{
+  long result = 0;
+  unsigned char c = 0;
+  unsigned char shift = 0;
+
+  if (!(**str)) {
+    return 0;
+  }
+
+  do {
+    c = (unsigned char)(*(*str)++)-'?';
+    result |= (c & 31)<<shift;
+    shift += 5;
+  } while (c & ~31 && **str);
+
+  if (result & 1) {
+    result = ~result;
+  }
+  return result/2;
+}
+
+void goog_poly_e(xg_string args, const QXmlStreamAttributes*)
+{
+  long lat = 0;
+  long lon = 0;
+  long level = 0;
+  long level1 = -9999;
+  long level2 = -9999;
+  char* str = encoded_points;
+  char* lstr = encoded_levels;
+
+  routehead[goog_segroute] = route_head_alloc();
+  route_add_head(routehead[goog_segroute]);
+  routecount[goog_segroute] = serial;
+
+  while (str && *str) {
+    lat += decode_goog64(&str);
+    lon += decode_goog64(&str);
+
+    level = -1;
+    level2 = level1;
+    if (lstr && *lstr) {
+      level1 = -decode_goog64(&lstr);
+    } else {
+      level1 = -9999;
+    }
+    level = (level1<level2)?level1:level2;
+
+    /* level of -9999 happens for endpoints */
+    if (level == -9999) {
+      level = 99999;
+    }
+
+    {
+      Waypoint* wpt_tmp = new Waypoint;
+      wpt_tmp->latitude = lat / 100000.0;
+      wpt_tmp->longitude = lon / 100000.0;
+      wpt_tmp->route_priority=level;
+      wpt_tmp->shortname = QString().sprintf( "\\%5.5x", serial++);
+      route_add_wpt(routehead[goog_segroute], wpt_tmp);
+    }
+  }
+
+}
+
+static void
+google_rd_init(const QString& fname)
+{
+  rd_fname = fname;
+
+  desc_handle = mkshort_new_handle();
+  setshort_length(desc_handle, 12);
+
+  xml_init(fname, google_map, NULL);
+}
+
+static void
+goog_read_file(void)
+{
+  QFile src(rd_fname);
+
+  src.open(QIODevice::ReadOnly);
+
+  QTextStream tstr(&src);
+  tstr.setCodec("ISO-8859-1");
+
+  QString preamble = tstr.read(256);
+  QString needle("http-equiv=\"content-type\" content=\"text/html; charset=");
+
+  if (!preamble.contains(needle)) {
+    // let QXmlStreamReader do its best if we can't figure it out...
+    xml_read();
+    return;
+  }
+
+  int idx = preamble.indexOf(needle);
+  QString charset = preamble.mid(idx + needle.length());
+
+  int endq = charset.indexOf('"');
+  if (endq != -1) {
+    charset = charset.left(endq);
+  }
+
+  QString wholefile;
+  if (charset == "ISO-8859-1") {
+    wholefile = preamble + tstr.readAll();
+  } else {
+    tstr.reset();
+    tstr.seek(0);
+    tstr.setCodec(CSTR(charset));
+    wholefile = tstr.readAll();
+  }
+
+  xml_readunicode(wholefile);
+}
+
+static void
+google_read(void)
+{
+  routehead = (route_head**)xmalloc(sizeof(route_head*));
+  routecount = (int*)xmalloc(sizeof(int));
+  goog_segroute = 0;
+  routehead[goog_segroute] = NULL;
+
+  goog_read_file();
+
+  xfree(routehead);
+  xfree(routecount);
+  routehead = NULL;
+  routecount = NULL;
+
+  if (encoded_points) {
+    xfree(encoded_points);
+    encoded_points = NULL;
+  }
+  if (encoded_levels) {
+    xfree(encoded_levels);
+    encoded_levels = NULL;
+  }
+  if (!script.isEmpty()) {
+    // TODO: rethink with Qt to make this less dependent on strchr...
+    char* s = xstrdup(script);
+    char* xml = strchr(s, '\'');
+    char* dict = strstr(s, "({");
+    char* end = NULL;
+
+    if (xml && (!dict || (xml < dict))) {
+      routehead = (route_head**)xmalloc(sizeof(route_head*));
+      routecount = (int*)xmalloc(sizeof(int));
+      goog_segroute = 0;
+      xml++;
+      end = strchr(xml+1, '\'');
+      if (end) {
+        *end = '\0';
+        xml_deinit();
+        xml_init(NULL, google_map, NULL);
+        xml_readstring(xml);
+        if (encoded_points) {
+          xfree(encoded_points);
+          encoded_points = NULL;
+        }
+        if (encoded_levels) {
+          xfree(encoded_levels);
+          encoded_levels = NULL;
+        }
+      }
+    } else if (dict) {
+      char qc = '\'';
+      int ofs = 9;
+      int panelofs = 8;
+      int count = 0;
+      char* tmp = NULL;
+      char* start = NULL;
+
+      char* panel = strstr(dict, "panel: '");
+      encoded_points = strstr(dict, "points: '");
+      encoded_levels = strstr(dict, "levels: '");
+      if (!encoded_points) {
+        ofs = 10;
+        qc = '"';
+        encoded_points = strstr(dict, "\"points\":\"");
+        encoded_levels = strstr(dict, "\"levels\":\"");
+        if (!encoded_points) {
+          encoded_points = strstr(dict, "points:\"");
+          encoded_levels = strstr(dict, "levels:\"");
+          ofs = 8;
+        }
+      }
+
+      if (!panel) {
+        panel = strstr(dict, "panel:\"");
+        panelofs = 7;
+      }
+      tmp=encoded_points;
+      while (tmp) {
+        if (qc == '"') {
+          char* tmp1 = strstr(tmp, "\"points\":\"");
+          if (!tmp1) {
+            tmp1 = strstr(tmp, "points:\"");
+          }
+          tmp = tmp1;
+        } else {
+          tmp = strstr(tmp, "points: '");
+        }
+        if (tmp) {
+          count++;
+          tmp++;
+        }
+      }
+      routehead = (route_head**)xmalloc(sizeof(route_head*)*count);
+      routecount = (int*)xmalloc(sizeof(int)*count);
+      goog_segroute = 0;
+
+      do {
+
+        if (encoded_points && encoded_levels) {
+          encoded_points += ofs;
+          encoded_levels += ofs;
+          end = strchr(encoded_points, qc);
+          if (end) {
+            *end = '\0';
+            end = encoded_points;
+            while ((end = strstr(end, "\\\\"))) {
+              memmove(end, end+1, strlen(end)+1);
+              end++;
+            }
+            end = strchr(encoded_levels, qc);
+            if (end) {
+              start = end;
+              *end = '\0';
+              end = encoded_levels;
+              while ((end = strstr(end, "\\\\"))) {
+                memmove(end, end+1, strlen(end)+1);
+                end++;
+              }
+              goog_poly_e(NULL, NULL);
+
+              goog_segroute++;
+              start++;
+              {
+                encoded_points = strstr(start, "points: '");
+                encoded_levels = strstr(start, "levels: '");
+              }
+              if (!encoded_points) {
+                encoded_points = strstr(start, "\"points\":\"");
+                encoded_levels = strstr(start, "\"levels\":\"");
+              }
+              if (!encoded_points) {
+                encoded_points = strstr(start, "points:\"");
+                encoded_levels = strstr(start, "levels:\"");
+              }
+            }
+          }
+        }
+      } while (start && encoded_points && encoded_levels);
+      if (panel) {
+        panel += panelofs;
+        end = strstr(panel, "/table><div class=\\\"legal");
+        if (!end) {
+          end = strstr(panel, "/table\\x3e\\x3cdiv class=\\\"legal");
+        }
+        if (!end) {
+          end = strstr(panel, "/table><div class=\\042legal");
+        }
+        if (!end) {
+          end = strstr(panel, "/table\\u003e\\u003cdiv id=\\\"mrDragRouteTip\\\"");
+        }
+        if (end) {
+          strcpy(end,"/table></div>");
+        }
+        if (!end) {
+          end = strstr(panel, "/div><div class=\\042legal");
+          if (end) {
+            strcpy(end, "/div></div>");
+          }
+        }
+        if (end) {
+          char* to = panel;
+          char* from = panel;
+          while (*from) {
+            if (!strncmp(from, "\\\"", 2)) {
+              *to++ = '"';
+              from += 2;
+              if (*(to-2) != '=') {
+                *to++ = ' ';
+              }
+            } else if (!strncmp(from, "\\042", 4)) {
+              *to++ = '"';
+              from += 4;
+
+              if (*(to-2) != '=') {
+                *to++ = ' ';
+              }
+            } else if (!strncmp(from, "\\u0026utm", 9)) {
+              strcpy(to, "&amp;utm");
+              to += 8;
+              from += 9;
+            } else if (!strncmp(from, "\\u0026", 6)) {
+              *to++='&';
+              from += 6;
+            } else if (!strncmp(from, "\\u003c", 6)) {
+              *to++='<';
+              from += 6;
+            } else if (!strncmp(from, "\\u003e", 6)) {
+              *to++='>';
+              from += 6;
+            } else if (!strncmp(from, "\\x", 2)) {
+              unsigned int c;
+              sscanf(from+2, "%2x", &c);
+              *to++ = (char)c;
+              from += 4;
+            } else if (!strncmp(from, "\\'", 2)) {
+              *to++ = '\'';
+              from += 2;
+            } else if (!strncmp(from, " nowrap ", 8)) {
+              *to++ = ' ';
+              from += 8;
+            } else if (!strncmp(from, "tr style=\\\"display:none", 23)) {
+              if (strcmp(to-5, "/tr><")) {
+                /* broken 6-26-07 missing </tr> that apparently doesn't bother browsers */
+                strcpy(to, "/tr><");
+                to += 5;
+              }
+              *to++ = *from++;
+            } else {
+              *to++ = *from++;
+            }
+          }
+          *to = '\0';
+
+#if 0
+          {
+            FILE* foo = fopen("foo.xml", "w");
+            fprintf(foo, "<!DOCTYPE foo [%s]>\n", xhtml_entities);
+            fwrite(panel, sizeof(char), strlen(panel), foo);
+            fclose(foo);
+          }
+#endif
+
+          xml_deinit();
+          xml_init(NULL, google_map, NULL);
+          xml_readprefixstring("<!DOCTYPE foo [");
+          xml_readprefixstring(xhtml_entities);
+          xml_readprefixstring("]>");
+          xml_readstring(panel);
+        }
+      }
+    }
+    script.clear();
+    xfree(routehead);
+    xfree(routecount);
+    xfree(s);
+  }
+
+  /*
+   * 'Tis better to leak than crash when we are merging and
+   * don't see an 'end' in the first file.  This feels a bit
+   * like plastering over a deeper problem...
+   *
+   */
+  if (encoded_points) {
+    encoded_points = NULL;
+  }
+  if (encoded_levels) {
+    encoded_levels = NULL;
+  }
+}
+
+static void
+google_rd_deinit(void)
+{
+  xml_deinit();
+  mkshort_del_handle(&desc_handle);
+  rd_fname.clear();
+}
+
+ff_vecs_t google_vecs = {
+  ff_type_file,
+  { ff_cap_none, ff_cap_read, ff_cap_none},
+  google_rd_init,
+  NULL,
+  google_rd_deinit,
+  NULL,
+  google_read,
+  NULL,
+  NULL,
+  NULL,
+  CET_CHARSET_UTF8, 1  /* CET-REVIEW */
+};
diff --git a/google.cc b/google.cc
deleted file mode 100644 (file)
index 1f8a9b5..0000000
--- a/google.cc
+++ /dev/null
@@ -1,556 +0,0 @@
-/*
-    Copyright (C) 2002-2014 Robert Lipe, robertlipe+source@gpsbabel.org
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
-
- */
-
-
-#include "defs.h"
-#include "xmlgeneric.h"
-#include <QtCore/QFile>
-#include <QtCore/QTextStream>
-#include <QtCore/QXmlStreamAttributes>
-
-static char* encoded_points = NULL;
-static char* encoded_levels = NULL;
-static QString script;
-static route_head** routehead;
-static int* routecount;
-static short_handle desc_handle;
-static QString rd_fname;
-
-static int serial = 0;
-
-#define MYNAME "google"
-
-static xg_callback      goog_points, goog_levels, goog_poly_e, goog_script;
-static xg_callback     goog_segment_s, goog_segment, goog_td_s, goog_td_b;
-static xg_callback     goog_td_e;
-
-static
-xg_tag_mapping google_map[] = {
-  { goog_points,  cb_cdata,       "/page/directions/polyline/points" },
-  { goog_levels,  cb_cdata,       "/page/directions/polyline/levels" },
-  { goog_poly_e,  cb_end,         "/page/directions/polyline" },
-  { goog_script,  cb_cdata,       "/html/head/script" },
-  { goog_segment_s, cb_start,      "/page/directions/segments/segment" },
-  { goog_segment, cb_cdata,      "/page/directions/segments/segment" },
-  { goog_td_s,    cb_start,      "/div/table/tr/td" },
-  { goog_td_s,    cb_start,      "/div/div/table/tr/td" },
-  { goog_td_b,      cb_cdata,      "/div/table/tr/td/b" },
-  { goog_td_b,      cb_cdata,      "/div/div/table/tr/td/b" },
-  { goog_td_e,    cb_end,        "/div/table/tr/td" },
-  { goog_td_e,    cb_end,        "/div/div/table/tr/td" },
-  { NULL, (xg_cb_type)0,              NULL }
-};
-
-void goog_script(xg_string args, const QXmlStreamAttributes*)
-{
-  script += args;
-}
-
-
-void goog_points(xg_string args, const QXmlStreamAttributes*)
-{
-  if (encoded_points) {
-    encoded_points = xstrappend(encoded_points, CSTRc(args));
-  } else {
-    encoded_points = xstrdup(args);
-  }
-}
-
-
-void goog_levels(xg_string args, const QXmlStreamAttributes*)
-{
-  if (encoded_levels) {
-    encoded_levels = xstrappend(encoded_levels, CSTRc(args));
-  } else {
-    encoded_levels = xstrdup(args);
-  }
-}
-
-static char goog_segname[7];
-static QString goog_realname;
-static int goog_segroute = 0;
-
-/*
- * The segments contain an index into the points array.  We use that
- * index to find the waypoint and insert a better name for it.
- */
-void goog_segment_s(xg_string args, const QXmlStreamAttributes* attrv)
-{
-  QStringRef ptidx = attrv->value("pointIndex");
-  if (!ptidx.isEmpty()) {
-    snprintf(goog_segname, sizeof(goog_segname), "\\%5.5x",
-             ptidx.toString().toUInt());
-  }
-}
-
-void goog_segment(xg_string args, const QXmlStreamAttributes*)
-{
-  Waypoint* wpt_tmp;
-
-  if (routehead[goog_segroute]) {
-    wpt_tmp = route_find_waypt_by_name(routehead[goog_segroute], goog_segname);
-    if (wpt_tmp) {
-      wpt_tmp->shortname = mkshort(desc_handle, args);
-      wpt_tmp->description = args;
-    }
-  }
-}
-
-void goog_td_s(xg_string args, const QXmlStreamAttributes* attrv)
-{
-  bool isdesc = false, isseg = false;
-  QStringRef aclass = attrv->value("class");
-  QStringRef id = attrv->value("id");
-
-  if (aclass.isEmpty() || id.isEmpty()) {
-    return;
-  }
-
-  isdesc = (aclass == "desc");
-  isseg = (aclass == "dirsegtext");
-
-  if (isdesc) {
-    QStringRef subid(id.string(), id.position() + 6, id.length() - 6);
-
-    goog_segroute = 0;
-    snprintf(goog_segname, sizeof(goog_segname), "\\%5.5x",
-             subid.toString().toUInt());
-  } else if (isseg) {
-    QString idstr = id.toString();
-    int first_us;
-
-    goog_segroute = 0;
-
-    first_us = idstr.indexOf("_");
-    if (idstr.indexOf("_", first_us + 1) != -1) {
-      goog_segroute = idstr.mid(first_us + 1).toUInt();
-    }
-
-    snprintf(goog_segname, sizeof(goog_segname), "\\%5.5x",
-             idstr.mid(idstr.lastIndexOf("_") + 1).toUInt() +
-             routecount[goog_segroute]);
-  }
-}
-
-void goog_td_b(xg_string args, const QXmlStreamAttributes*)
-{
-  if (goog_segname[0] == '\\' && !strchr(CSTRc(args), '\xa0')) {
-    goog_realname = args;
-  }
-}
-
-void goog_td_e(xg_string args, const QXmlStreamAttributes*)
-{
-  if (goog_segname[0] == '\\' && !goog_realname.isEmpty()) {
-    goog_segment(goog_realname, NULL/*unused*/);
-  }
-  goog_segname[0] = '\0';
-  goog_realname.clear();
-}
-
-static long decode_goog64(char** str)
-{
-  long result = 0;
-  unsigned char c = 0;
-  unsigned char shift = 0;
-
-  if (!(**str)) {
-    return 0;
-  }
-
-  do {
-    c = (unsigned char)(*(*str)++)-'?';
-    result |= (c & 31)<<shift;
-    shift += 5;
-  } while (c & ~31 && **str);
-
-  if (result & 1) {
-    result = ~result;
-  }
-  return result/2;
-}
-
-void goog_poly_e(xg_string args, const QXmlStreamAttributes*)
-{
-  long lat = 0;
-  long lon = 0;
-  long level = 0;
-  long level1 = -9999;
-  long level2 = -9999;
-  char* str = encoded_points;
-  char* lstr = encoded_levels;
-
-  routehead[goog_segroute] = route_head_alloc();
-  route_add_head(routehead[goog_segroute]);
-  routecount[goog_segroute] = serial;
-
-  while (str && *str) {
-    lat += decode_goog64(&str);
-    lon += decode_goog64(&str);
-
-    level = -1;
-    level2 = level1;
-    if (lstr && *lstr) {
-      level1 = -decode_goog64(&lstr);
-    } else {
-      level1 = -9999;
-    }
-    level = (level1<level2)?level1:level2;
-
-    /* level of -9999 happens for endpoints */
-    if (level == -9999) {
-      level = 99999;
-    }
-
-    {
-      Waypoint* wpt_tmp = new Waypoint;
-      wpt_tmp->latitude = lat / 100000.0;
-      wpt_tmp->longitude = lon / 100000.0;
-      wpt_tmp->route_priority=level;
-      wpt_tmp->shortname = QString().sprintf( "\\%5.5x", serial++);
-      route_add_wpt(routehead[goog_segroute], wpt_tmp);
-    }
-  }
-
-}
-
-static void
-google_rd_init(const QString& fname)
-{
-  rd_fname = fname;
-
-  desc_handle = mkshort_new_handle();
-  setshort_length(desc_handle, 12);
-
-  xml_init(fname, google_map, NULL);
-}
-
-static void
-goog_read_file(void)
-{
-  QFile src(rd_fname);
-
-  src.open(QIODevice::ReadOnly);
-
-  QTextStream tstr(&src);
-  tstr.setCodec("ISO-8859-1");
-
-  QString preamble = tstr.read(256);
-  QString needle("http-equiv=\"content-type\" content=\"text/html; charset=");
-
-  if (!preamble.contains(needle)) {
-    // let QXmlStreamReader do its best if we can't figure it out...
-    xml_read();
-    return;
-  }
-
-  int idx = preamble.indexOf(needle);
-  QString charset = preamble.mid(idx + needle.length());
-
-  int endq = charset.indexOf('"');
-  if (endq != -1) {
-    charset = charset.left(endq);
-  }
-
-  QString wholefile;
-  if (charset == "ISO-8859-1") {
-    wholefile = preamble + tstr.readAll();
-  } else {
-    tstr.reset();
-    tstr.seek(0);
-    tstr.setCodec(CSTR(charset));
-    wholefile = tstr.readAll();
-  }
-
-  xml_readunicode(wholefile);
-}
-
-static void
-google_read(void)
-{
-  routehead = (route_head**)xmalloc(sizeof(route_head*));
-  routecount = (int*)xmalloc(sizeof(int));
-  goog_segroute = 0;
-  routehead[goog_segroute] = NULL;
-
-  goog_read_file();
-
-  xfree(routehead);
-  xfree(routecount);
-  routehead = NULL;
-  routecount = NULL;
-
-  if (encoded_points) {
-    xfree(encoded_points);
-    encoded_points = NULL;
-  }
-  if (encoded_levels) {
-    xfree(encoded_levels);
-    encoded_levels = NULL;
-  }
-  if (!script.isEmpty()) {
-    // TODO: rethink with Qt to make this less dependent on strchr...
-    char* s = xstrdup(script);
-    char* xml = strchr(s, '\'');
-    char* dict = strstr(s, "({");
-    char* end = NULL;
-
-    if (xml && (!dict || (xml < dict))) {
-      routehead = (route_head**)xmalloc(sizeof(route_head*));
-      routecount = (int*)xmalloc(sizeof(int));
-      goog_segroute = 0;
-      xml++;
-      end = strchr(xml+1, '\'');
-      if (end) {
-        *end = '\0';
-        xml_deinit();
-        xml_init(NULL, google_map, NULL);
-        xml_readstring(xml);
-        if (encoded_points) {
-          xfree(encoded_points);
-          encoded_points = NULL;
-        }
-        if (encoded_levels) {
-          xfree(encoded_levels);
-          encoded_levels = NULL;
-        }
-      }
-    } else if (dict) {
-      char qc = '\'';
-      int ofs = 9;
-      int panelofs = 8;
-      int count = 0;
-      char* tmp = NULL;
-      char* start = NULL;
-
-      char* panel = strstr(dict, "panel: '");
-      encoded_points = strstr(dict, "points: '");
-      encoded_levels = strstr(dict, "levels: '");
-      if (!encoded_points) {
-        ofs = 10;
-        qc = '"';
-        encoded_points = strstr(dict, "\"points\":\"");
-        encoded_levels = strstr(dict, "\"levels\":\"");
-        if (!encoded_points) {
-          encoded_points = strstr(dict, "points:\"");
-          encoded_levels = strstr(dict, "levels:\"");
-          ofs = 8;
-        }
-      }
-
-      if (!panel) {
-        panel = strstr(dict, "panel:\"");
-        panelofs = 7;
-      }
-      tmp=encoded_points;
-      while (tmp) {
-        if (qc == '"') {
-          char* tmp1 = strstr(tmp, "\"points\":\"");
-          if (!tmp1) {
-            tmp1 = strstr(tmp, "points:\"");
-          }
-          tmp = tmp1;
-        } else {
-          tmp = strstr(tmp, "points: '");
-        }
-        if (tmp) {
-          count++;
-          tmp++;
-        }
-      }
-      routehead = (route_head**)xmalloc(sizeof(route_head*)*count);
-      routecount = (int*)xmalloc(sizeof(int)*count);
-      goog_segroute = 0;
-
-      do {
-
-        if (encoded_points && encoded_levels) {
-          encoded_points += ofs;
-          encoded_levels += ofs;
-          end = strchr(encoded_points, qc);
-          if (end) {
-            *end = '\0';
-            end = encoded_points;
-            while ((end = strstr(end, "\\\\"))) {
-              memmove(end, end+1, strlen(end)+1);
-              end++;
-            }
-            end = strchr(encoded_levels, qc);
-            if (end) {
-              start = end;
-              *end = '\0';
-              end = encoded_levels;
-              while ((end = strstr(end, "\\\\"))) {
-                memmove(end, end+1, strlen(end)+1);
-                end++;
-              }
-              goog_poly_e(NULL, NULL);
-
-              goog_segroute++;
-              start++;
-              {
-                encoded_points = strstr(start, "points: '");
-                encoded_levels = strstr(start, "levels: '");
-              }
-              if (!encoded_points) {
-                encoded_points = strstr(start, "\"points\":\"");
-                encoded_levels = strstr(start, "\"levels\":\"");
-              }
-              if (!encoded_points) {
-                encoded_points = strstr(start, "points:\"");
-                encoded_levels = strstr(start, "levels:\"");
-              }
-            }
-          }
-        }
-      } while (start && encoded_points && encoded_levels);
-      if (panel) {
-        panel += panelofs;
-        end = strstr(panel, "/table><div class=\\\"legal");
-        if (!end) {
-          end = strstr(panel, "/table\\x3e\\x3cdiv class=\\\"legal");
-        }
-        if (!end) {
-          end = strstr(panel, "/table><div class=\\042legal");
-        }
-        if (!end) {
-          end = strstr(panel, "/table\\u003e\\u003cdiv id=\\\"mrDragRouteTip\\\"");
-        }
-        if (end) {
-          strcpy(end,"/table></div>");
-        }
-        if (!end) {
-          end = strstr(panel, "/div><div class=\\042legal");
-          if (end) {
-            strcpy(end, "/div></div>");
-          }
-        }
-        if (end) {
-          char* to = panel;
-          char* from = panel;
-          while (*from) {
-            if (!strncmp(from, "\\\"", 2)) {
-              *to++ = '"';
-              from += 2;
-              if (*(to-2) != '=') {
-                *to++ = ' ';
-              }
-            } else if (!strncmp(from, "\\042", 4)) {
-              *to++ = '"';
-              from += 4;
-
-              if (*(to-2) != '=') {
-                *to++ = ' ';
-              }
-            } else if (!strncmp(from, "\\u0026utm", 9)) {
-              strcpy(to, "&amp;utm");
-              to += 8;
-              from += 9;
-            } else if (!strncmp(from, "\\u0026", 6)) {
-              *to++='&';
-              from += 6;
-            } else if (!strncmp(from, "\\u003c", 6)) {
-              *to++='<';
-              from += 6;
-            } else if (!strncmp(from, "\\u003e", 6)) {
-              *to++='>';
-              from += 6;
-            } else if (!strncmp(from, "\\x", 2)) {
-              unsigned int c;
-              sscanf(from+2, "%2x", &c);
-              *to++ = (char)c;
-              from += 4;
-            } else if (!strncmp(from, "\\'", 2)) {
-              *to++ = '\'';
-              from += 2;
-            } else if (!strncmp(from, " nowrap ", 8)) {
-              *to++ = ' ';
-              from += 8;
-            } else if (!strncmp(from, "tr style=\\\"display:none", 23)) {
-              if (strcmp(to-5, "/tr><")) {
-                /* broken 6-26-07 missing </tr> that apparently doesn't bother browsers */
-                strcpy(to, "/tr><");
-                to += 5;
-              }
-              *to++ = *from++;
-            } else {
-              *to++ = *from++;
-            }
-          }
-          *to = '\0';
-
-#if 0
-          {
-            FILE* foo = fopen("foo.xml", "w");
-            fprintf(foo, "<!DOCTYPE foo [%s]>\n", xhtml_entities);
-            fwrite(panel, sizeof(char), strlen(panel), foo);
-            fclose(foo);
-          }
-#endif
-
-          xml_deinit();
-          xml_init(NULL, google_map, NULL);
-          xml_readprefixstring("<!DOCTYPE foo [");
-          xml_readprefixstring(xhtml_entities);
-          xml_readprefixstring("]>");
-          xml_readstring(panel);
-        }
-      }
-    }
-    script.clear();
-    xfree(routehead);
-    xfree(routecount);
-    xfree(s);
-  }
-
-  /*
-   * 'Tis better to leak than crash when we are merging and
-   * don't see an 'end' in the first file.  This feels a bit
-   * like plastering over a deeper problem...
-   *
-   */
-  if (encoded_points) {
-    encoded_points = NULL;
-  }
-  if (encoded_levels) {
-    encoded_levels = NULL;
-  }
-}
-
-static void
-google_rd_deinit(void)
-{
-  xml_deinit();
-  mkshort_del_handle(&desc_handle);
-  rd_fname.clear();
-}
-
-ff_vecs_t google_vecs = {
-  ff_type_file,
-  { ff_cap_none, ff_cap_read, ff_cap_none},
-  google_rd_init,
-  NULL,
-  google_rd_deinit,
-  NULL,
-  google_read,
-  NULL,
-  NULL,
-  NULL,
-  CET_CHARSET_UTF8, 1  /* CET-REVIEW */
-};
diff --git a/testo.d/deprecated/google.test b/testo.d/deprecated/google.test
new file mode 100644 (file)
index 0000000..997cb44
--- /dev/null
@@ -0,0 +1,26 @@
+#
+# Google Maps XML test
+#
+rm -f ${TMPDIR}/google.out
+gpsbabel -i google -f ${REFERENCE}/google.xml -o csv -F ${TMPDIR}/google.out
+compare ${REFERENCE}/google.csv ${TMPDIR}/google.out
+
+rm -f ${TMPDIR}/google.out
+gpsbabel -i google -f ${REFERENCE}/google.js -o csv -F ${TMPDIR}/google.out
+compare ${REFERENCE}/google.csv ${TMPDIR}/google.out
+
+rm -f ${TMPDIR}/google.out
+gpsbabel -i google -f ${REFERENCE}/google_jan_06.html -o csv -F ${TMPDIR}/google.out
+compare ${REFERENCE}/google_jan_06.csv ${TMPDIR}/google.out
+
+rm -f ${TMPDIR}/google.out
+gpsbabel -i google -f ${REFERENCE}/google_multisegment.js -o gpx -F ${TMPDIR}/google.out
+compare ${REFERENCE}/google_multisegment.gpx ${TMPDIR}/google.out
+
+rm -f ${TMPDIR}/google.out
+gpsbabel -i google -f ${REFERENCE}/google_multisegment_utf8.js -o gpx -F ${TMPDIR}/google.out
+compare ${REFERENCE}/google_multisegment.gpx ${TMPDIR}/google.out
+
+rm -f ${TMPDIR}/google.out
+gpsbabel -i google -f ${REFERENCE}/google_multisegment_utf16.js -o gpx -F ${TMPDIR}/google.out
+compare ${REFERENCE}/google_multisegment.gpx ${TMPDIR}/google.out
diff --git a/testo.d/google.test b/testo.d/google.test
deleted file mode 100644 (file)
index 997cb44..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# Google Maps XML test
-#
-rm -f ${TMPDIR}/google.out
-gpsbabel -i google -f ${REFERENCE}/google.xml -o csv -F ${TMPDIR}/google.out
-compare ${REFERENCE}/google.csv ${TMPDIR}/google.out
-
-rm -f ${TMPDIR}/google.out
-gpsbabel -i google -f ${REFERENCE}/google.js -o csv -F ${TMPDIR}/google.out
-compare ${REFERENCE}/google.csv ${TMPDIR}/google.out
-
-rm -f ${TMPDIR}/google.out
-gpsbabel -i google -f ${REFERENCE}/google_jan_06.html -o csv -F ${TMPDIR}/google.out
-compare ${REFERENCE}/google_jan_06.csv ${TMPDIR}/google.out
-
-rm -f ${TMPDIR}/google.out
-gpsbabel -i google -f ${REFERENCE}/google_multisegment.js -o gpx -F ${TMPDIR}/google.out
-compare ${REFERENCE}/google_multisegment.gpx ${TMPDIR}/google.out
-
-rm -f ${TMPDIR}/google.out
-gpsbabel -i google -f ${REFERENCE}/google_multisegment_utf8.js -o gpx -F ${TMPDIR}/google.out
-compare ${REFERENCE}/google_multisegment.gpx ${TMPDIR}/google.out
-
-rm -f ${TMPDIR}/google.out
-gpsbabel -i google -f ${REFERENCE}/google_multisegment_utf16.js -o gpx -F ${TMPDIR}/google.out
-compare ${REFERENCE}/google_multisegment.gpx ${TMPDIR}/google.out